/** * @file display.c * Display module -- this implements display handling for MCE *

* Copyright (c) 2007 - 2011 Nokia Corporation and/or its subsidiary(-ies). * Copyright (c) 2012 - 2023 Jolla Ltd. * Copyright (c) 2020 Open Mobile Platform LLC. *

* @author David Weinehall * @author Tapio Rantala * @author Santtu Lakkala * @author Jukka Turunen * @author Irina Bezruk * @author Markus Lehtonen * @author Kalle Jokiniemi * @author Philippe De Swert * @author Philippe De Swert * @author Simo Piiroinen * @author Martin Kampas * @author Pekka Lundstrom * * 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 "display.h" #include "../mce-log.h" #include "../mce-io.h" #include "../mce-lib.h" #include "../mce-dbus.h" #include "../mce-fbdev.h" #include "../mce-common.h" #include "../mce-conf.h" #include "../mce-setting.h" #include "../mce-dbus.h" #include "../mce-sensorfw.h" #include "../mce-wltimer.h" #include "../tklock.h" #ifdef ENABLE_HYBRIS # include "../mce-hybris.h" #endif #include "../mce-worker.h" #include "../filewatcher.h" #ifdef ENABLE_WAKELOCKS # include "../libwakelock.h" #endif #include "powersavemode.h" #include "../systemui/dbus-names.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #if 0 // DEBUG: make all logging from this module "critical" # undef mce_log # define mce_log(LEV, FMT, ARGS...) \ mce_log_file(LL_CRIT, __FILE__, __FUNCTION__, FMT , ## ARGS) #endif /* ========================================================================= * * CONSTANTS * ========================================================================= */ /** Module name */ #define MODULE_NAME "display" /** UI side graphics fading percentage * * Controls maximum opacity of the black box rendered on top at the ui * when backlight dimming alone is not enough to make dimmed display * state visible to the user. */ #define MCE_FADER_MAXIMUM_OPACITY_PERCENT 50 /** Backlight fade animation duration after brightness setting changes */ #define MCE_FADER_DURATION_SETTINGS_CHANGED 600 /** Minimum duration for direct backlight fade animation */ #define MCE_FADER_DURATION_HW_MIN 0 /** Maximum duration for direct backlight fade animation */ #define MCE_FADER_DURATION_HW_MAX 5000 /** Minimum duration for compositor based ui dimming animation */ #define MCE_FADER_DURATION_UI_MIN 100 /** Maximum duration for compositor based ui dimming animation */ #define MCE_FADER_DURATION_UI_MAX 5000 /** How long to delay entering late suspend after powering down display */ #define MCE_DISPLAY_STM_SUSPEND_DELAY_NS (5LL * 1000LL * 1000LL * 1000LL) /** Placeholder value for unknown compositor pid */ #define COMPOSITOR_STM_INVALID_PID (-1) /** Panic led delay for freshly started compositor process * * During bootup it is more or less expected that compositor is * unable to answer immediately. So we allow freshly started * compositor process longer delay and bring it down gradually * to target level. */ #define COMPOSITOR_STM_INITIAL_PANIC_DELAY 15000 /** Panic led delay for already established compositor process * * With each successfull ipc communication with the compositor, * the expected ipc delay is lowered until the minimum is reached. */ #define COMPOSITOR_STM_MINIMUM_PANIC_DELAY 3000 /** How long to wait for reply to setUpdatesEnabled() calls * * There is no way to robustly deal with situations where compositor * replies get delayed. If the compositor side recovers from whatever * is delaying the dbus methdod call processing, it will see the already * send enable/disable rendering call. Any attempts to retry / change * direction once a request has been send can only lead to mce and * compositor processes getting out of sync regarding updates enabled * status. * * So the best option is to wait until we get a reply or compositor * process gets restarted. */ # define COMPOSITOR_STM_DBUS_CALL_TIMEOUT DBUS_TIMEOUT_INFINITE /** How long to delay retrying after failed setUpdatesEnabled() calls * * Few seconds delay makes sure mce does not go amok with retries if * there should still be some hiccup with compositor dbus service. */ #define COMPOSITOR_STM_DBUS_RETRY_DELAY 5000 /** How long hiding applications is allowed to take on entry to LPM * * The LPM UI is effectively the same as lockscreen. If some application * remains on top after LPM activation, we have ghost UI instead of LPM. * * The known problem spot is: Call UI after ending a call. * * The delay should be long enough not to cause false positives, and * short enough not to leave users too much time to try to interact * with an application that is not going to get any touch events. */ #define LPM_SANITIZE_DELAY 600 // [ms] /* ========================================================================= * * TYPEDEFS * ========================================================================= */ /** Display type */ typedef enum { /** Display type unset */ DISPLAY_TYPE_UNSET = -1, /** No display available; XXX should never happen */ DISPLAY_TYPE_NONE = 0, /** Generic display interface without CABC */ DISPLAY_TYPE_GENERIC = 1, /** EID l4f00311 with CABC */ DISPLAY_TYPE_L4F00311 = 2, /** Sony acx565akm with CABC */ DISPLAY_TYPE_ACX565AKM = 3, /** Taal display */ DISPLAY_TYPE_TAAL = 4, /** Himalaya display */ DISPLAY_TYPE_HIMALAYA = 5, /** Generic display name */ DISPLAY_TYPE_DISPLAY0 = 6, /** Generic name for ACPI-controlled displays */ DISPLAY_TYPE_ACPI_VIDEO0 = 7 } display_type_t; /** * CABC mapping; D-Bus API modes vs SysFS mode */ typedef struct { /** CABC mode D-Bus name */ const gchar *const dbus; /** CABC mode SysFS name */ const gchar *const sysfs; /** CABC mode available */ gboolean available; } cabc_mode_mapping_t; /** Enumeration of possible setUpdatesEnabled() synchronization states */ typedef enum { /** setUpdatesEnabled() call failed */ RENDERER_ERROR = -2, /** setUpdatesEnabled() not attempted yet */ RENDERER_UNKNOWN = -1, /** setUpdatesEnabled(false) successfully made */ RENDERER_DISABLED = 0, /** setUpdatesEnabled(true) successfully made */ RENDERER_ENABLED = 1, } renderer_state_t; /** Enumeration for compositor related debug led patterns */ typedef enum { /** Getting reply to setUpdatesEnabled(true) is taking too long */ COMPOSITOR_LED_ENABLE_PANIC, /** Getting reply to setUpdatesEnabled(false) is taking too long */ COMPOSITOR_LED_DISABLE_PANIC, /** Killing unresponsive compositor process */ COMPOSITOR_LED_KILLER_ACTIVE, /** Number of compositor related debug led patterns */ COMPOSITOR_LED_NUMOF, } compositor_led_t; /** Enumeration of states mce <-> compositor ipc can be in */ typedef enum { /** Waiting for D-Bus service availability */ COMPOSITOR_STATE_INITIAL, /** About to be destroyed */ COMPOSITOR_STATE_FINAL, /** Compositor D-Bus service is not running */ COMPOSITOR_STATE_STOPPED, /** Compositor D-Bus service is running */ COMPOSITOR_STATE_STARTED, /** HW Compositor start/stop actions */ COMPOSITOR_STATE_SETUP, /** Pending setUpdatesEnabled() D-Bus method call */ COMPOSITOR_STATE_REQUESTING, /** Successfully finished setUpdatesEnabled() D-Bus method call */ COMPOSITOR_STATE_GRANTED, /** Received error reply to setUpdatesEnabled() D-Bus method call */ COMPOSITOR_STATE_FAILED, /** Number of compositor ipc related states */ COMPOSITOR_STATE_NUMOF } compositor_state_t; /** State information for frame buffer resume waiting */ typedef struct { /** frame buffer suspended flag */ bool suspended; /** worker thread id */ pthread_t thread; /** worker thread done flag */ bool finished; /** path to fb wakeup event file */ const char *wake_path; /** wakeup file descriptor */ int wake_fd; /** path to fb sleep event file */ const char *sleep_path; /** sleep file descriptor */ int sleep_fd; /** write end of wakeup mainloop pipe */ int pipe_fd; /** pipe reader io watch id */ guint pipe_id; } waitfb_t; /** Possible values for bootstate */ typedef enum { BOOTSTATE_UNKNOWN, BOOTSTATE_USER, BOOTSTATE_ACT_DEAD, } bootstate_t; /** Content and where to write it */ typedef struct governor_setting_t { /** Path (or rather glob pattern) to file where to write */ char *path; /** Data to write */ char *data; } governor_setting_t; /** Display state machine states */ typedef enum { STM_UNSET, STM_RENDERER_INIT_START, STM_RENDERER_WAIT_START, STM_ENTER_POWER_ON, STM_STAY_POWER_ON, STM_LEAVE_POWER_ON, STM_RENDERER_INIT_STOP, STM_RENDERER_WAIT_STOP, STM_WAIT_FADE_TO_BLACK, STM_WAIT_FADE_TO_TARGET, STM_INIT_SUSPEND, STM_WAIT_SUSPEND, STM_ENTER_POWER_OFF, STM_STAY_POWER_OFF, STM_LEAVE_POWER_OFF, STM_INIT_RESUME, STM_WAIT_RESUME, STM_ENTER_LOGICAL_OFF, STM_STAY_LOGICAL_OFF, STM_LEAVE_LOGICAL_OFF, } stm_state_t; /** Delays for display blank/unblank related debug led patterns [ms] */ enum { /** How long to wait for framebuffer sleep/wake event from kernel */ LED_DELAY_FB_SUSPEND_RESUME = 1000, /** How long to wait dbus method call reply from lipstick */ LED_DELAY_UI_DISABLE_ENABLE_MINIMUM = 3000, /** How long to initially wait dbus method call reply from lipstick */ LED_DELAY_UI_DISABLE_ENABLE_MAXIMUM = 15000, }; /** Backlight fade types */ typedef enum { /** No brightness fading */ FADER_IDLE, /** Normal brightness fading */ FADER_DEFAULT, /** Fading to MCE_DISPLAY_DIM */ FADER_DIMMING, /** Fading due to ALS adjustment */ FADER_ALS, /** Fading to DISPLAY_OFF */ FADER_BLANK, /** Fading from DISPLAY_OFF */ FADER_UNBLANK, FADER_NUMOF } fader_type_t; /* ------------------------------------------------------------------------- * * BLANKING_PAUSE_CLIENT * ------------------------------------------------------------------------- */ typedef struct bpclient_t bpclient_t; struct bpclient_t { char *bpc_name; pid_t bpc_pid; int64_t bpc_tmo; }; /* ========================================================================= * * PROTOTYPES * ========================================================================= */ /* ------------------------------------------------------------------------- * * MISC_UTILS * ------------------------------------------------------------------------- */ static inline bool mdy_str_eq_p(const char *s1, const char *s2); static const char *blanking_pause_mode_repr(blanking_pause_mode_t mode); static const char *fader_type_name(fader_type_t type); static int xlat(int src_lo, int src_hi, int dst_lo, int dst_hi, int val); /* ------------------------------------------------------------------------- * * SHUTDOWN * ------------------------------------------------------------------------- */ static bool mdy_shutdown_in_progress(void); /* ------------------------------------------------------------------------- * * DATAPIPE_TRACKING * ------------------------------------------------------------------------- */ static void mdy_datapipe_packagekit_locked_cb(gconstpointer data);; static void mdy_datapipe_system_state_cb(gconstpointer data); static void mdy_datapipe_submode_cb(gconstpointer data); static void mdy_datapipe_interaction_expected_cb(gconstpointer data); static void mdy_datapipe_lid_sensor_filtered_cb(gconstpointer data); static gpointer mdy_datapipe_display_state_filter_cb(gpointer data); static void mdy_datapipe_display_state_curr_cb(gconstpointer data); static void mdy_datapipe_display_state_next_cb(gconstpointer data); static void mdy_datapipe_keyboard_slide_input_state_cb(gconstpointer const data); static void mdy_datapipe_keyboard_available_state_cb(gconstpointer const data); static void mdy_datapipe_display_brightness_cb(gconstpointer data); static void mdy_datapipe_lpm_brightness_cb(gconstpointer data); static void mdy_datapipe_display_state_req_cb(gconstpointer data); static void mdy_datapipe_audio_route_cb(gconstpointer data); static void mdy_datapipe_charger_state_cb(gconstpointer data); static void mdy_datapipe_uiexception_type_cb(gconstpointer data); static void mdy_datapipe_alarm_ui_state_cb(gconstpointer data); static void mdy_datapipe_proximity_sensor_actual_cb(gconstpointer data); static void mdy_datapipe_power_saving_mode_active_cb(gconstpointer data); static void mdy_datapipe_call_state_trigger_cb(gconstpointer data); static void mdy_datapipe_device_inactive_cb(gconstpointer data); static void mdy_datapipe_orientation_sensor_actual_cb(gconstpointer data); static void mdy_datapipe_shutting_down_cb(gconstpointer aptr); static void mdy_datapipe_execute_brightness(void); static void mdy_datapipe_init(void); static void mdy_datapipe_quit(void); /* ------------------------------------------------------------------------- * * FBDEV_FD * ------------------------------------------------------------------------- */ static void mdy_fbdev_rethink(void); /* ------------------------------------------------------------------------- * * LOW_POWER_MODE * ------------------------------------------------------------------------- */ static bool mdy_lpm_allowed(void); static gboolean mdy_lpm_sanitize_cb(gpointer aptr); static void mdy_lpm_schedule_sanitize(void); static void mdy_lpm_cancel_sanitize(void); /* ------------------------------------------------------------------------- * * HIGH_BRIGHTNESS_MODE * ------------------------------------------------------------------------- */ static void mdy_hbm_set_level(int number); static gboolean mdy_hbm_timeout_cb(gpointer data); static void mdy_hbm_cancel_timeout(void); static void mdy_hbm_schedule_timeout(void); static void mdy_hbm_rethink(void); /* ------------------------------------------------------------------------- * * BACKLIGHT_BRIGHTNESS * ------------------------------------------------------------------------- */ static gboolean mdy_brightness_retry_cb(gpointer aptr); static void mdy_brightness_cancel_retry(void); static void mdy_brightness_schedule_retry(int level); #ifdef ENABLE_HYBRIS static bool mdy_brightness_set_level_hybris(int number); #endif static bool mdy_brightness_set_level_default(int number); static int mdy_brightness_normalize_level(int number); static void mdy_brightness_set_level(int number); static void mdy_brightness_forget_level(void); static void mdy_brightness_fade_continue_with_als(fader_type_t fader_type); static void mdy_brightness_force_level(int number); static void mdy_brightness_set_priority_boost(bool enable); static gboolean mdy_brightness_fade_timer_cb(gpointer data); static void mdy_brightness_cleanup_fade_timer(void); static void mdy_brightness_stop_fade_timer(void); static void mdy_brightness_start_fade_timer(fader_type_t type, gint step_time); static bool mdy_brightness_fade_is_active(void); static bool mdy_brightness_is_fade_allowed(fader_type_t type); static void mdy_brightness_set_fade_target_ex(fader_type_t type, gint new_brightness, gint transition_time); static void mdy_brightness_set_fade_target_default(gint new_brightness); static void mdy_brightness_set_fade_target_dimming(gint new_brightness); static void mdy_brightness_set_fade_target_als(gint new_brightness); static void mdy_brightness_set_fade_target_blank(void); static void mdy_brightness_set_fade_target_unblank(gint new_brightness); static int mdy_brightness_get_dim_static(void); static int mdy_brightness_get_dim_dynamic(void); static int mdy_brightness_get_dim_threshold_lo(void); static int mdy_brightness_get_dim_threshold_hi(void); static void mdy_brightness_set_on_level(gint hbm_and_level); static void mdy_brightness_set_dim_level(void); static void mdy_brightness_set_lpm_level(gint level); static void mdy_brightness_init(void); /* ------------------------------------------------------------------------- * * UI_SIDE_DIMMING * ------------------------------------------------------------------------- */ static void mdy_ui_dimming_set_level(int level); static void mdy_ui_dimming_rethink(void); /* ------------------------------------------------------------------------- * * CONTENT_ADAPTIVE_BACKLIGHT_CONTROL * ------------------------------------------------------------------------- */ static void mdy_cabc_mode_set(const gchar *mode); /* ------------------------------------------------------------------------- * * BOOTUP_LED_PATTERN * ------------------------------------------------------------------------- */ static void mdy_poweron_led_rethink(void); static gboolean mdy_poweron_led_rethink_cb(gpointer aptr); static void mdy_poweron_led_rethink_cancel(void); static void mdy_poweron_led_rethink_schedule(void); /* ------------------------------------------------------------------------- * * BLANKING_PAUSE_CLIENT * ------------------------------------------------------------------------- */ void bpclient_update_pid_cb (const peerinfo_t *peerinfo, gpointer userdata); void bpclient_update_timeout(bpclient_t *self); bpclient_t *bpclient_create (const char *name); void bpclient_delete (bpclient_t *self); void bpclient_delete_cb (void *self); /* ------------------------------------------------------------------------- * * AUTOMATIC_BLANKING * ------------------------------------------------------------------------- */ /** Maximum dim timeout applicable in ACTDEAD mode [s] */ #define ACTDEAD_MAX_DIM_TIMEOUT 15 /** Maximum blank timeout applicable in ACTDEAD mode [s] */ #define ACTDEAD_MAX_OFF_TIMEOUT 15 static bool mdy_blanking_from_lockscreen(void); static void mdy_blanking_update_device_inactive_delay(void); static gboolean mdy_blanking_can_blank_from_low_power_mode(void); // display timer: ON -> DIM static gint mdy_blanking_get_default_dimming_delay(void); static gint mdy_blanking_get_dimming_delay(void); static gboolean mdy_blanking_dim_cb(gpointer data); static void mdy_blanking_cancel_dim(void); static void mdy_blanking_schedule_dim(void); static gboolean mdy_blanking_inhibit_broadcast_cb(gpointer aptr); static void mdy_blanking_inhibit_cancel_broadcast(void); static void mdy_blanking_inhibit_schedule_broadcast(void); // display timer: DIM -> OFF static gboolean mdy_blanking_off_cb(gpointer data); static void mdy_blanking_cancel_off(void); static void mdy_blanking_schedule_off(void); // display timer: LPM_ON -> LPM_OFF (when proximity covered) static gboolean mdy_blanking_lpm_off_cb(gpointer data); static void mdy_blanking_cancel_lpm_off(void); static void mdy_blanking_schedule_lpm_off(void); // blanking pause period: inhibit automatic ON -> DIM transitions static void mdy_blanking_pause_evaluate_allowed(void); static gboolean mdy_blanking_pause_period_cb(gpointer data); static void mdy_blanking_stop_pause_period(void); static void mdy_blanking_start_pause_period(int duration); static void mdy_blanking_init_pause_client_tracking(void); static void mdy_blanking_quit_pause_client_tracking(void); static bool mdy_blanking_is_paused(void); static bool mdy_blanking_pause_can_dim(void); static bool mdy_blanking_pause_is_allowed(void); static void mdy_blanking_add_pause_client(const char *name); static void mdy_blanking_remove_pause_client(const char *name); static void mdy_blanking_remove_pause_clients(void); static void mdy_blanking_evaluate_pause_timeout(void); // adaptive dimming period: dimming timeouts get longer on ON->DIM->ON transitions static bool mdy_adaptive_dimming_is_enabled(void); static gint mdy_blanking_get_adaptive_dimming_delay(void); static void mdy_blanking_set_adaptive_dimming_delay(int timeout); static void mdy_blanking_reset_adaptive_dimming_delay(void); static void mdy_blanking_trigger_adaptive_dimming(void); static gboolean mdy_blanking_adaptive_dimming_unprime_cb(gpointer data); static void mdy_blanking_unprime_adaptive_dimming(void); static void mdy_blanking_prime_adaptive_dimming(void); // display timer: all of em static bool mdy_blanking_inhibit_off_p(void); static bool mdy_blanking_inhibit_dim_p(void); static void mdy_blanking_rethink_timers(bool force); static void mdy_blanking_rethink_proximity(void); static void mdy_blanking_cancel_timers(void); // after boot blank prevent static bool mdy_blanking_afterboot_delay_start_p(void); static void mdy_blanking_rethink_afterboot_delay(void); static gint mdy_blanking_get_afterboot_delay(void); /* ------------------------------------------------------------------------- * * DISPLAY_TYPE_PROBING * ------------------------------------------------------------------------- */ static int mdy_display_type_glob_err_cb(const char *path, int err); static gboolean mdy_display_type_probe_brightness(const gchar *dirpath, char **setpath, char **maxpath); static gboolean mdy_display_type_get_from_config(display_type_t *display_type); static gboolean mdy_display_type_get_from_sysfs_probe(display_type_t *display_type); static gboolean mdy_display_type_get_from_hybris(display_type_t *display_type); static display_type_t mdy_display_type_get(void); /* ------------------------------------------------------------------------- * * FBDEV_SLEEP_AND_WAKEUP * ------------------------------------------------------------------------- */ #ifdef ENABLE_WAKELOCKS static gboolean mdy_waitfb_event_cb(GIOChannel *chn, GIOCondition cnd, gpointer aptr); static void *mdy_waitfb_thread_entry(void *aptr); static gboolean mdy_waitfb_thread_start(waitfb_t *self); static void mdy_waitfb_thread_stop(waitfb_t *self); #endif /* ------------------------------------------------------------------------- * * TOPMOST_WINDOW_TRACKING * ------------------------------------------------------------------------- */ static void mdy_topmost_window_set_pid (int pid); static gboolean mdy_topmost_window_pid_changed_cb (DBusMessage *const sig); static void mdy_topmost_window_pid_reply_cb (DBusPendingCall *pc, void *aptr); static void mdy_topmost_window_forget_pid_query (void); static void mdy_topmost_window_send_pid_query (void); /* ------------------------------------------------------------------------- * * compositor_led_t * ------------------------------------------------------------------------- */ static void compositor_led_set_active (compositor_led_t led, bool active); static bool compositor_led_is_active (compositor_led_t led); /* ------------------------------------------------------------------------- * * compositor_state_t * ------------------------------------------------------------------------- */ static const char *compositor_state_repr (compositor_state_t state); /* ------------------------------------------------------------------------- * * renderer_state_t * ------------------------------------------------------------------------- */ static const char *renderer_state_repr (renderer_state_t state); /* ------------------------------------------------------------------------- * * compositor_stm_t * ------------------------------------------------------------------------- */ typedef struct compositor_stm_t compositor_stm_t; static gboolean compositor_stm_linger_timeout_cb (gpointer aptr); static void compositor_stm_cancel_linger_timeout (compositor_stm_t *self); static void compositor_stm_schedule_linger_timeout (compositor_stm_t *self); static void compositor_stm_lingerer_info_cb (const peerinfo_t *peerinfo, gpointer aptr); static const char *compositor_stm_get_lingerer (const compositor_stm_t *self); static void compositor_stm_set_lingerer (compositor_stm_t *self, const char *name); static void compositor_stm_ctor (compositor_stm_t *self); static void compositor_stm_dtor (compositor_stm_t *self); compositor_stm_t *compositor_stm_create (void); void compositor_stm_delete (compositor_stm_t *self); void compositor_stm_delete_cb (void *self); static void compositor_stm_set_service_pid (compositor_stm_t *self, pid_t pid); static void compositor_stm_set_service_owner (compositor_stm_t *self, const char *owner); static const char *compositor_stm_get_service_owner (const compositor_stm_t *self); static void compositor_stm_set_service_actions(compositor_stm_t *self, unsigned actions); static bool compositor_stm_pending_pid_query(const compositor_stm_t *self); static void compositor_stm_send_pid_query (compositor_stm_t *self); static void compositor_stm_forget_pid_query (compositor_stm_t *self); static void compositor_stm_pid_query_cb (DBusPendingCall *pc, void *aptr); static bool compositor_stm_pending_actions_query(const compositor_stm_t *self); static void compositor_stm_send_actions_query(compositor_stm_t *self); static void compositor_stm_forget_actions_query(compositor_stm_t *self); static void compositor_stm_actions_query_cb (DBusPendingCall *pc, void *aptr); static void compositor_stm_send_ctrl_request (compositor_stm_t *self); static void compositor_stm_forget_ctrl_request(compositor_stm_t *self); static void compositor_stm_ctrl_request_cb (DBusPendingCall *pc, void *aptr); static void compositor_stm_schedule_panic (compositor_stm_t *self); static void compositor_stm_cancel_panic (compositor_stm_t *self); static gboolean compositor_stm_panic_timer_cb (void *aptr); static void compositor_stm_schedule_retry (compositor_stm_t *self); static void compositor_stm_cancel_retry (compositor_stm_t *self); static gboolean compositor_stm_retry_timer_cb (void *aptr); static void compositor_stm_schedule_killer (compositor_stm_t *self); static void compositor_stm_cancel_killer (compositor_stm_t *self); static gboolean compositor_stm_core_timer_cb (void *aptr); static gboolean compositor_stm_kill_timer_cb (void *aptr); static gboolean compositor_stm_bury_timer_cb (void *aptr); static compositor_state_t compositor_stm_get_state (const compositor_stm_t *self); static void compositor_stm_set_state (compositor_stm_t *self, compositor_state_t state); static void compositor_stm_enter_state (compositor_stm_t *self); static void compositor_stm_leave_state (compositor_stm_t *self); static gboolean compositor_stm_eval_state_cb (gpointer aptr); static void compositor_stm_eval_state (compositor_stm_t *self); static void compositor_stm_set_target (compositor_stm_t *self, renderer_state_t state); static void compositor_stm_set_requested (compositor_stm_t *self, renderer_state_t state); static void compositor_stm_set_granted (compositor_stm_t *self, renderer_state_t state); static bool compositor_stm_is_pending (const compositor_stm_t *self); static bool compositor_stm_is_enabled (const compositor_stm_t *self); static bool compositor_stm_is_disabled (const compositor_stm_t *self); static void compositor_stm_set_enabled (compositor_stm_t *self, bool enable); static void compositor_stm_init_config (void); static void compositor_stm_quit_config (void); static int compositor_stm_execute (const char *command); static void *compositor_stm_action_exec_cb (void *aptr); static void compositor_stm_action_done_cb (void *aptr, void *reply); /* ------------------------------------------------------------------------- * * COMPOSITOR_IPC * ------------------------------------------------------------------------- */ static void mdy_compositor_init(void); static void mdy_compositor_quit(void); static void mdy_compositor_set_name_owner(const char *curr); static void mdy_compositor_disable(void); static void mdy_compositor_enable(void); static bool mdy_compositor_is_available(void); static bool mdy_compositor_is_pending(void); static bool mdy_compositor_is_disabled(void); static bool mdy_compositor_is_enabled(void); /* ------------------------------------------------------------------------- * * CALLSTATE_CHANGES * ------------------------------------------------------------------------- */ enum { /** Default duration for blocking suspend after call_state changes */ CALLSTATE_CHANGE_BLOCK_SUSPEND_DEFAULT_MS = 15 * 1000, /** Duration for blocking suspend after call_state changes to active */ CALLSTATE_CHANGE_BLOCK_SUSPEND_ACTIVE_MS = 60 * 1000, }; static gboolean mdy_callstate_end_changed_cb(gpointer aptr); static bool mdy_callstate_changed_recently(void); static void mdy_callstate_clear_changed(void); static void mdy_callstate_set_changed(void); /* ------------------------------------------------------------------------- * * AUTOSUSPEND_POLICY * ------------------------------------------------------------------------- */ #ifdef ENABLE_WAKELOCKS static int mdy_autosuspend_get_allowed_level(void); static void mdy_autosuspend_setting_cb(GConfClient *const client, const guint id, GConfEntry *const entry, gpointer const data); #endif /* ------------------------------------------------------------------------- * * ORIENTATION_ACTIVITY * ------------------------------------------------------------------------- */ static void mdy_orientation_changed_cb(int state); static void mdy_orientation_generate_activity(void); static bool mdy_orientation_sensor_wanted(void); static void mdy_orientation_sensor_rethink(void); /* ------------------------------------------------------------------------- * * DISPLAY_STATE * ------------------------------------------------------------------------- */ static void mdy_display_state_changed(void); static void mdy_display_state_enter(display_state_t prev_state, display_state_t next_state); static void mdy_display_state_leave(display_state_t prev_state, display_state_t next_state); /* ------------------------------------------------------------------------- * * FRAMEBUFFER_SUSPEND_RESUME * ------------------------------------------------------------------------- */ /** Framebuffer suspend/resume failure led patterns */ typedef enum { FBDEV_LED_OFF, FBDEV_LED_SUSPENDING, FBDEV_LED_RESUMING, } mdy_fbsusp_led_state_t; static void mdy_fbsusp_led_set(mdy_fbsusp_led_state_t req); static gboolean mdy_fbsusp_led_timer_cb(gpointer aptr); static void mdy_fbsusp_led_cancel_timer(void); static void mdy_fbsusp_led_start_timer(mdy_fbsusp_led_state_t req); /* ------------------------------------------------------------------------- * * DISPLAY_STATE_MACHINE * ------------------------------------------------------------------------- */ // human readable state names static const char *mdy_stm_state_name(stm_state_t state); // react to systemui availability changes static void mdy_datapipe_compositor_service_state_cb(gconstpointer aptr); static void mdy_datapipe_lipstick_service_state_cb(gconstpointer aptr); // whether there is unhandled compositor availability change static void mdy_stm_set_compositor_availability_changed(bool changed); // whether to power on/off the frame buffer static bool mdy_stm_display_state_needs_power(display_state_t state); // asynchronous ioctl framebuffer power control static void mdy_stm_fbdev_power_done_cb(void *aptr, void *reply); static void *mdy_stm_fbdev_power_exec_cb(void *aptr); static void mdy_stm_fbdev_set_power(bool poweron); // asynchronous sysfs write to allow/deny suspending static void mdy_stm_autosuspend_done_cb(void *aptr, void *reply); static void *mdy_stm_autosuspend_exec_cb(void *aptr); static void mdy_stm_autosuspend_set_state(bool allow); // early/late suspend and resume static bool mdy_stm_is_early_suspend_allowed(void); static bool mdy_stm_is_late_suspend_allowed(void); static void mdy_stm_start_fb_suspend(void); static bool mdy_stm_is_fb_suspend_finished(void); static void mdy_stm_start_fb_resume(void); static bool mdy_stm_is_fb_resume_finished(void); static void mdy_stm_release_wakelock(void); static void mdy_stm_acquire_wakelock(void); // display state changing static void mdy_stm_push_target_change(display_state_t next_state); static bool mdy_stm_pull_target_change(void); static void mdy_stm_finish_target_change(void); // actual state machine static void mdy_stm_trans(stm_state_t state); static void mdy_stm_step(void); static void mdy_stm_exec(void); // delayed state machine execution static gboolean mdy_stm_rethink_cb(gpointer aptr); static void mdy_stm_cancel_rethink(void); static void mdy_stm_schedule_rethink(void); static void mdy_stm_force_rethink(void); /* ------------------------------------------------------------------------- * * DISPLAY_STATE_STATISTICS * ------------------------------------------------------------------------- */ static void mdy_statistics_update(void); /* ------------------------------------------------------------------------- * * CPU_SCALING_GOVERNOR * ------------------------------------------------------------------------- */ static governor_setting_t *mdy_governor_get_settings(const char *tag); static void mdy_governor_free_settings(governor_setting_t *settings); static bool mdy_governor_write_data(const char *path, const char *data); static void mdy_governor_apply_setting(const governor_setting_t *setting); static void mdy_governor_set_state(int state); static void mdy_governor_rethink(void); static void mdy_governor_setting_cb(GConfClient *const client, const guint id, GConfEntry *const entry, gpointer const data); /* ------------------------------------------------------------------------- * * DBUS_HANDLERS * ------------------------------------------------------------------------- */ static gboolean mdy_dbus_send_blanking_pause_status(DBusMessage *const method_call); static gboolean mdy_dbus_send_blanking_pause_allowed_status(DBusMessage *const method_call); static gboolean mdy_dbus_handle_blanking_pause_get_req(DBusMessage *const msg); static gboolean mdy_dbus_handle_blanking_pause_allowed_get_req(DBusMessage *const msg); static gboolean mdy_dbus_send_blanking_inhibit_status(DBusMessage *const method_call); static gboolean mdy_dbus_handle_blanking_inhibit_get_req(DBusMessage *const msg); static void mdy_dbus_invalidate_display_status(void); static gboolean mdy_dbus_send_display_status(DBusMessage *const method_call); static const char *mdy_dbus_get_reason_to_block_display_on(void); static const char *mdy_dbus_get_reason_to_block_display_off(void); static const char *mdy_dbus_get_reason_to_block_display_lpm(void); static void mdy_dbus_handle_display_state_req(display_state_t state); static void mdy_dbus_handle_display_state_req_cb(gpointer aptr); static void mdy_dbus_schedule_display_state_req(DBusMessage *const msg, display_state_t state); static gboolean mdy_dbus_handle_display_on_req(DBusMessage *const msg); static gboolean mdy_dbus_handle_display_dim_req(DBusMessage *const msg); static gboolean mdy_dbus_handle_display_off_req(DBusMessage *const msg); static gboolean mdy_dbus_handle_display_lpm_req(DBusMessage *const msg); #ifdef ENABLE_DEVEL_LOGGING static gboolean mdy_dbus_handle_display_lpm_on_req(DBusMessage *const msg); static gboolean mdy_dbus_handle_display_lpm_off_req(DBusMessage *const msg); #endif static gboolean mdy_dbus_handle_display_status_get_req(DBusMessage *const msg); static gboolean mdy_dbus_send_cabc_mode(DBusMessage *const method_call); static gboolean mdy_dbus_handle_cabc_mode_owner_lost_sig(DBusMessage *const msg); static gboolean mdy_dbus_handle_cabc_mode_get_req(DBusMessage *const msg); static gboolean mdy_dbus_handle_cabc_mode_set_req(DBusMessage *const msg); static gboolean mdy_dbus_handle_blanking_pause_start_req(DBusMessage *const msg); static gboolean mdy_dbus_handle_blanking_pause_cancel_req(DBusMessage *const msg); static gboolean mdy_dbus_handle_display_stats_get_req(DBusMessage *const req); static gboolean mdy_dbus_handle_desktop_started_sig(DBusMessage *const msg); static void mdy_dbus_init(void); static void mdy_dbus_quit(void); /* ------------------------------------------------------------------------- * * FLAG_FILE_TRACKING * ------------------------------------------------------------------------- */ static gboolean mdy_flagfiles_desktop_ready_cb(gpointer user_data); static void mdy_flagfiles_bootstate_cb(const char *path, const char *file, gpointer data); static void mdy_flagfiles_init_done_cb(const char *path, const char *file, gpointer data); static void mdy_flagfiles_osupdate_running_cb(const char *path, const char *file, gpointer data); static void mdy_flagfiles_start_tracking(void); static void mdy_flagfiles_stop_tracking(void); /* ------------------------------------------------------------------------- * * DYNAMIC_SETTINGS * ------------------------------------------------------------------------- */ static void mdy_setting_cb(GConfClient *const gcc, const guint id, GConfEntry *const entry, gpointer const data); static void mdy_setting_init(void); static void mdy_setting_quit(void); static void mdy_setting_sanitize_brightness_levels(void); static void mdy_setting_sanitize_dim_timeouts(bool force_update); /* ------------------------------------------------------------------------- * * MODULE_LOAD_UNLOAD * ------------------------------------------------------------------------- */ G_MODULE_EXPORT const gchar *g_module_check_init(GModule *module); G_MODULE_EXPORT void g_module_unload(GModule *module); /* ========================================================================= * * VARIABLES * ========================================================================= */ /** Display blank prevention timer */ static const gint mdy_blank_prevent_timeout = BLANK_PREVENT_TIMEOUT * 1000; static const gint mdy_blank_prevent_slack = BLANK_PREVENT_SLACK * 1000; /* ------------------------------------------------------------------------- * * MODULE_LOAD_UNLOAD * ------------------------------------------------------------------------- */ /** Functionality provided by this module */ static const gchar *const provides[] = { MODULE_NAME, NULL }; /** Module information */ G_MODULE_EXPORT module_info_struct module_info = { /** Name of the module */ .name = MODULE_NAME, /** Module provides */ .provides = provides, /** Module priority */ .priority = 250 }; /* ------------------------------------------------------------------------- * * SHUTDOWN * ------------------------------------------------------------------------- */ /** Have we seen shutdown_ind signal or equivalent from dsme */ static bool mdy_shutdown_started_flag = false; /** Start of shutdown timestamp */ static int64_t mdy_shutdown_started_tick = 0; /** Are we already unloading the module? */ static gboolean mdy_unloading_module = FALSE; /** Timer for waiting simulated desktop ready state */ static guint mdy_desktop_ready_id = 0; /** Device is shutting down predicate */ static bool mdy_shutdown_in_progress(void) { return mdy_shutdown_started_flag; } /* ------------------------------------------------------------------------- * * BLANKING_PAUSE_CLIENT * ------------------------------------------------------------------------- */ /** Callback for handling blanking pause client state change notifications * * @param peerinfo peerinfo_t object holding currently known peer details * @param userdata bpclient_t object as void pointer */ void bpclient_update_pid_cb(const peerinfo_t *peerinfo, gpointer userdata) { bpclient_t *self = userdata; peerstate_t state = peerinfo_get_state(peerinfo); pid_t pid = peerinfo_get_owner_pid(peerinfo); mce_log(LL_DEBUG, "client %s @%s pid=%d", self->bpc_name, peerstate_repr(state), (int)pid); switch( state ) { case PEERSTATE_STOPPED: mdy_blanking_remove_pause_client(self->bpc_name), self = 0; goto EXIT; default: break; } if( self->bpc_pid != pid ) { self->bpc_pid = pid; mdy_blanking_evaluate_pause_timeout(); } EXIT: return; } /** Renew timeout for blanking pause client * * @param self bpclient_t object */ void bpclient_update_timeout(bpclient_t *self) { mce_log(LL_DEBUG, "client %s renewed", self->bpc_name); int64_t tmo = mce_lib_get_boot_tick() + mdy_blank_prevent_timeout; if( self->bpc_tmo != tmo ) { self->bpc_tmo = tmo; mdy_blanking_evaluate_pause_timeout(); } } /** Create object for holding blanking pause client state * * @param name D-Bus name of the client */ bpclient_t * bpclient_create(const char *name) { bpclient_t *self = calloc(1, sizeof *self); self->bpc_name = strdup(name); self->bpc_pid = -1; self->bpc_tmo = 0; mce_log(LL_DEBUG, "client %s added", self->bpc_name); mce_dbus_name_tracker_add(self->bpc_name, bpclient_update_pid_cb, self, 0); return self; } /** Delete object for holding blanking pause client state * * @param self bpclient_t object */ void bpclient_delete(bpclient_t *self) { if( !self ) goto EXIT; mce_log(LL_DEBUG, "client %s removed", self->bpc_name); mce_dbus_name_tracker_remove(self->bpc_name, bpclient_update_pid_cb, self); free(self->bpc_name), self->bpc_name = 0; free(self); EXIT: return; } /** Type agnostic callback for deleting blanking pause client objects * * @param self bpclient_t object */ void bpclient_delete_cb(void *self) { bpclient_delete(self); } /* ------------------------------------------------------------------------- * * AUTOMATIC_BLANKING * ------------------------------------------------------------------------- */ /** File used to enable low power mode */ static gchar *mdy_low_power_mode_file = NULL; /** Is display low power mode supported * * Since we now support proximity based fake lpm that does not * require special support from display hw / drivers, the logic * for mdy_low_power_mode_supported can be switched to: enabled * by default and disabled only in special cases. */ static gboolean mdy_low_power_mode_supported = TRUE; /** Maximum number of monitored services that calls blanking pause */ #define BLANKING_PAUSE_MAX_MONITORED 5 /* ------------------------------------------------------------------------- * * HIGH_BRIGHTNESS_MODE * ------------------------------------------------------------------------- */ /** File used to set high brightness mode */ static output_state_t mdy_high_brightness_mode_output = { .context = "high_brightness_mode", .truncate_file = TRUE, .close_on_exit = FALSE, }; /** Is display high brightness mode supported */ static gboolean mdy_high_brightness_mode_supported = FALSE; /* ------------------------------------------------------------------------- * * CONTENT_ADAPTIVE_BACKLIGHT_CONTROL * ------------------------------------------------------------------------- */ /** Is content adaptive brightness control supported */ static gboolean mdy_cabc_is_supported = FALSE; /** File used to get the available CABC modes */ static gchar *mdy_cabc_available_modes_file = NULL; /** CABC mode -- uses the SysFS mode names */ static const gchar *mdy_cabc_mode_def = DEFAULT_CABC_MODE; /** CABC mode when power save mode is active */ static const gchar *mdy_cabc_mode_psm = DEFAULT_PSM_CABC_MODE; /** File used to set the CABC mode */ static gchar *mdy_cabc_mode_file = NULL; /** List of monitored CABC mode requesters */ static GSList *mdy_cabc_mode_monitor_list = NULL; /* ------------------------------------------------------------------------- * * FLAG_FILE_TRACKING * ------------------------------------------------------------------------- */ /** Are we going to USER or ACT_DEAD */ static bootstate_t mdy_bootstate = BOOTSTATE_UNKNOWN; /** Content change watcher for the bootstate flag file */ static filewatcher_t *mdy_bootstate_watcher = 0; /** Is the init-done flag file present in the file system */ static tristate_t mdy_init_done = TRISTATE_UNKNOWN; /** Content change watcher for the init-done flag file */ static filewatcher_t *mdy_init_done_watcher = 0; /** Is the update-mode flag file present in the file system */ static gboolean mdy_osupdate_running = FALSE; /** Content change watcher for the update-mode flag file */ static filewatcher_t *mdy_osupdate_running_watcher = 0; /* ------------------------------------------------------------------------- * * DYNAMIC_SETTINGS * ------------------------------------------------------------------------- */ /** Setting for statically defined dimmed screen brightness */ static gint mdy_brightness_dim_static = MCE_DEFAULT_DISPLAY_DIM_STATIC_BRIGHTNESS; static guint mdy_brightness_dim_static_setting_id = 0; /** Setting for dimmed screen brightness defined as portion of on brightness*/ static gint mdy_brightness_dim_dynamic = MCE_DEFAULT_DISPLAY_DIM_DYNAMIC_BRIGHTNESS; static guint mdy_brightness_dim_dynamic_setting_id = 0; /** Setting for start compositor dimming threshold */ static gint mdy_brightness_dim_compositor_lo = MCE_DEFAULT_DISPLAY_DIM_COMPOSITOR_LO; static guint mdy_brightness_dim_compositor_lo_setting_id = 0; /** Setting for use maximum compositor dimming threshold */ static gint mdy_brightness_dim_compositor_hi= MCE_DEFAULT_DISPLAY_DIM_COMPOSITOR_HI; static guint mdy_brightness_dim_compositor_hi_setting_id = 0; /** Setting for allowing dimming while blanking is paused */ static gint mdy_blanking_pause_mode = MCE_DEFAULT_DISPLAY_BLANKING_PAUSE_MODE; static guint mdy_blanking_pause_mode_setting_id = 0; /** Flag: Disable automatic blanking from lockscreen */ static gint mdy_blanking_from_tklock_disabled = MCE_DEFAULT_TK_AUTO_BLANK_DISABLE; static guint mdy_blanking_from_tklock_disabled_setting_id = 0; /** Display blanking timeout setting */ static gint mdy_blank_timeout = MCE_DEFAULT_DISPLAY_BLANK_TIMEOUT; static guint mdy_blank_timeout_setting_id = 0; /** Display blanking timeout from lockscreen setting */ static gint mdy_blank_from_lockscreen_timeout = MCE_DEFAULT_DISPLAY_BLANK_FROM_LOCKSCREEN_TIMEOUT; static guint mdy_blank_from_lockscreen_timeout_setting_id = 0; /** Display blanking timeout from lpm-on setting */ static gint mdy_blank_from_lpm_on_timeout = MCE_DEFAULT_DISPLAY_BLANK_FROM_LPM_ON_TIMEOUT; static guint mdy_blank_from_lpm_on_timeout_setting_id = 0; /** Display blanking timeout from lpm-off setting */ static gint mdy_blank_from_lpm_off_timeout = MCE_DEFAULT_DISPLAY_BLANK_FROM_LPM_OFF_TIMEOUT; static guint mdy_blank_from_lpm_off_timeout_setting_id = 0; /** Number of brightness steps */ static gint mdy_brightness_step_count = MCE_DEFAULT_DISPLAY_BRIGHTNESS_LEVEL_COUNT; static guint mdy_brightness_step_count_setting_id = 0; /** Size of one brightness step */ static gint mdy_brightness_step_size = MCE_DEFAULT_DISPLAY_BRIGHTNESS_LEVEL_SIZE; static guint mdy_brightness_step_size_setting_id = 0; /** display brightness setting; [1, mdy_brightness_step_count] */ static gint mdy_brightness_setting = MCE_DEFAULT_DISPLAY_BRIGHTNESS; static guint mdy_brightness_setting_setting_id = 0; /** timestamp of the latest brightness setting change */ static int64_t mdy_brightness_setting_change_time = 0; /** Tracking ID for auto-brightness setting * * Used only for updating mdy_brightness_setting_change_time */ static guint mdy_automatic_brightness_setting_setting_id = 0; /** Never blank display setting */ static gint mdy_disp_never_blank = MCE_DEFAULT_DISPLAY_NEVER_BLANK; static guint mdy_disp_never_blank_setting_id = 0; /** Use adaptive timeouts for dimming */ static gboolean mdy_adaptive_dimming_enabled = MCE_DEFAULT_DISPLAY_ADAPTIVE_DIMMING; static guint mdy_adaptive_dimming_enabled_setting_id = 0; /** Array of possible display dim timeouts */ static GSList *mdy_possible_dim_timeouts = NULL; static guint mdy_possible_dim_timeouts_setting_id = 0; /** Threshold to use for adaptive timeouts for dimming in milliseconds */ static gint mdy_adaptive_dimming_threshold = MCE_DEFAULT_DISPLAY_ADAPTIVE_DIM_THRESHOLD; static guint mdy_adaptive_dimming_threshold_setting_id = 0; /** Display dimming timeout setting */ static gint mdy_disp_dim_timeout_default = MCE_DEFAULT_DISPLAY_DIM_TIMEOUT; static guint mdy_disp_dim_timeout_default_setting_id = 0; /** Display dimming timeout for keyboard available setting */ static gint mdy_disp_dim_timeout_keyboard = MCE_DEFAULT_DISPLAY_DIM_WITH_KEYBOARD_TIMEOUT; static guint mdy_disp_dim_timeout_keyboard_setting_id = 0; /** Current adaptive display dimming delay */ static gint mdy_disp_dim_timeout_adaptive = 0; /** Use low power mode setting */ static gboolean mdy_use_low_power_mode = MCE_DEFAULT_USE_LOW_POWER_MODE; static guint mdy_use_low_power_mode_setting_id = 0; /** Display blanking inhibit mode */ static inhibit_t mdy_blanking_inhibit_mode = MCE_DEFAULT_BLANKING_INHIBIT_MODE; static guint mdy_blanking_inhibit_mode_setting_id = 0; /** Kbd slide display blanking inhibit mode */ static gint mdy_kbd_slide_inhibit_mode = MCE_DEFAULT_KBD_SLIDE_INHIBIT; static guint mdy_kbd_slide_inhibit_mode_setting_id = 0; /** Override mode for display off requests made over D-Bus */ static gint mdy_dbus_display_off_override = MCE_DEFAULT_DISPLAY_OFF_OVERRIDE; static guint mdy_dbus_display_off_override_setting_id = 0; /* ========================================================================= * * MISC_UTILS * ========================================================================= */ /** Null tolerant string equality predicate */ static inline bool mdy_str_eq_p(const char *s1, const char *s2) { // Note: mdy_str_eq_p(NULL, NULL) -> false on purpose return (s1 && s2) ? !strcmp(s1, s2) : false; } /** Convert blanking_pause_mode_t enum to human readable string * * @param mode blanking_pause_mode_t enumeration value * * @return human readable representation of mode */ static const char *blanking_pause_mode_repr(blanking_pause_mode_t mode) { const char *res = "unknown"; switch( mode ) { case BLANKING_PAUSE_MODE_DISABLED: res = "disabled"; break; case BLANKING_PAUSE_MODE_KEEP_ON: res = "keep-on"; break; case BLANKING_PAUSE_MODE_ALLOW_DIM: res = "allow-dim"; break; default: break; } return res; } /** Convert fader_type_t enum to human readable string * * @param state fader_type_t enumeration value * * @return human readable representation of the value */ static const char * fader_type_name(fader_type_t type) { static const char * const lut[FADER_NUMOF] = { [FADER_IDLE] = "IDLE", [FADER_DEFAULT] = "DEFAULT", [FADER_DIMMING] = "DIMMING", [FADER_ALS] = "ALS", [FADER_BLANK] = "BLANK", [FADER_UNBLANK] = "UNBLANK", }; return (type < FADER_NUMOF) ? lut[type] : "INVALID"; } /** Map value in one range to another using linear interpolation * * Assumes src_lo < src_hi, if this is not the case * low boundary of the target range is returned. * * If provided value is outside the source range, output * is capped to the given target range. * * @param src_lo low boundary of the source range * @param src_hi high boundary of the source range * @param dst_lo low boundary of the target range * @param dst_hi high boundary of the target range * @param val value in [src_lo, src_hi] range * * @return val mapped to [dst_lo, dst_hi] range */ static int xlat(int src_lo, int src_hi, int dst_lo, int dst_hi, int val) { if( src_lo > src_hi ) return dst_lo; if( val <= src_lo ) return dst_lo; if( val >= src_hi ) return dst_hi; int range = src_hi - src_lo; val = (val - src_lo) * dst_hi + (src_hi - val) * dst_lo; val += range/2; val /= range; return val; } /* ========================================================================= * * DATAPIPE_TRACKING * ========================================================================= */ /** Cached exceptional ui state */ static uiexception_type_t uiexception_type = UIEXCEPTION_TYPE_NONE; /** Cached PID of process owning the topmost window on UI */ static int topmost_window_pid = -1; /** Cached lipstick availability; assume unknown */ static service_state_t lipstick_service_state = SERVICE_STATE_UNDEF; /** PackageKit Locked property is set to true during sw updates */ static bool packagekit_locked = false; /** * Handle packagekit_locked_pipe notifications * * @param data The locked state stored in a pointer */ static void mdy_datapipe_packagekit_locked_cb(gconstpointer data) { bool prev = packagekit_locked; packagekit_locked = GPOINTER_TO_INT(data); if( packagekit_locked == prev ) goto EXIT; /* Log by default as it might help analyzing upgrade problems */ mce_log(LL_WARN, "packagekit_locked = %d", packagekit_locked); /* re-evaluate suspend policy */ mdy_stm_schedule_rethink(); EXIT: return; } /* Cached system state */ static system_state_t system_state = MCE_SYSTEM_STATE_UNDEF; /** * Handle system_state_pipe notifications * * @param data The system state stored in a pointer */ static void mdy_datapipe_system_state_cb(gconstpointer data) { system_state_t prev = system_state; system_state = GPOINTER_TO_INT(data); if( system_state == prev ) goto EXIT; mce_log(LL_DEBUG, "system_state: %s -> %s", system_state_repr(prev), system_state_repr(system_state)); /* re-evaluate suspend policy */ mdy_stm_schedule_rethink(); /* Deal with ACTDEAD alarms / not in USER mode */ mdy_fbdev_rethink(); #ifdef ENABLE_CPU_GOVERNOR mdy_governor_rethink(); #endif mdy_blanking_rethink_afterboot_delay(); EXIT: return; } /* Assume we are in mode transition when mce starts up */ static submode_t submode = MCE_SUBMODE_TRANSITION; /** * Handle submode_pipe notifications * * @param data The submode stored in a pointer */ static void mdy_datapipe_submode_cb(gconstpointer data) { submode_t prev = submode; submode = GPOINTER_TO_INT(data); if( submode == prev ) goto EXIT; mce_log(LL_DEBUG, "submode = %s", submode_change_repr(prev, submode)); mdy_blanking_pause_evaluate_allowed(); /* Rethink dim/blank timers if tklock state changed */ if( (prev ^ submode) & MCE_SUBMODE_TKLOCK ) mdy_blanking_rethink_timers(false); submode_t old_trans = prev & MCE_SUBMODE_TRANSITION; submode_t new_trans = submode & MCE_SUBMODE_TRANSITION; if( old_trans && !new_trans ) { /* End of transition; stable state reached */ // force blanking timer reprogramming mdy_blanking_rethink_timers(true); } EXIT: return; } /** Interaction expected; assume false */ static bool interaction_expected = false; /** Change notifications for interaction_expected_pipe */ static void mdy_datapipe_interaction_expected_cb(gconstpointer data) { bool prev = interaction_expected; interaction_expected = GPOINTER_TO_INT(data); if( prev == interaction_expected ) goto EXIT; mce_log(LL_DEBUG, "interaction_expected: %d -> %d", prev, interaction_expected); mdy_blanking_pause_evaluate_allowed(); mdy_blanking_rethink_timers(false); EXIT: return; } /** Cache Lid cover policy state; assume unknown */ static cover_state_t lid_sensor_filtered = COVER_UNDEF; /** Change notifications from lid_sensor_filtered_pipe */ static void mdy_datapipe_lid_sensor_filtered_cb(gconstpointer data) { cover_state_t prev = lid_sensor_filtered; lid_sensor_filtered = GPOINTER_TO_INT(data); if( lid_sensor_filtered == prev ) goto EXIT; mce_log(LL_DEBUG, "lid_sensor_filtered = %s -> %s", cover_state_repr(prev), cover_state_repr(lid_sensor_filtered)); EXIT: return; } /* Cached current display state */ static display_state_t display_state_curr = MCE_DISPLAY_UNDEF; /* Cached target display state */ static display_state_t display_state_next = MCE_DISPLAY_UNDEF; /** Filter display_state_request_pipe changes * * @param data The unfiltered display state stored in a pointer * * @return The filtered display state stored in a pointer */ static gpointer mdy_datapipe_display_state_filter_cb(gpointer data) { display_state_t want_state = GPOINTER_TO_INT(data); display_state_t next_state = want_state; /* Handle never-blank override */ if( mdy_disp_never_blank ) { next_state = MCE_DISPLAY_ON; goto UPDATE; } /* Handle update-mode override */ if( mdy_osupdate_running ) { next_state = MCE_DISPLAY_ON; goto UPDATE; } /* Validate requested display state */ switch( next_state ) { case MCE_DISPLAY_OFF: case MCE_DISPLAY_DIM: case MCE_DISPLAY_ON: break; case MCE_DISPLAY_LPM_OFF: case MCE_DISPLAY_LPM_ON: if( mdy_lpm_allowed() ) break; mce_log(LL_DEBUG, "reject low power mode display request"); next_state = MCE_DISPLAY_OFF; goto UPDATE; default: case MCE_DISPLAY_UNDEF: case MCE_DISPLAY_POWER_UP: case MCE_DISPLAY_POWER_DOWN: mce_log(LL_WARN, "reject invalid display mode request"); next_state = MCE_DISPLAY_OFF; goto UPDATE; } /* Allow display off / no change */ if( next_state == MCE_DISPLAY_OFF || next_state == display_state_curr ) goto UPDATE; /* Keep existing state if display on requests are made during * mce/device startup and device shutdown/reboot. */ if( system_state == MCE_SYSTEM_STATE_UNDEF ) { /* But initial state = ON/OFF selection at display plugin * initialization must still be allowed */ if( display_state_curr == MCE_DISPLAY_UNDEF ) { if( next_state != MCE_DISPLAY_ON ) next_state = MCE_DISPLAY_OFF; } else { mce_log(LL_WARN, "reject display mode request at start up"); next_state = display_state_curr; } } else if( (submode & MCE_SUBMODE_TRANSITION) && ( system_state == MCE_SYSTEM_STATE_SHUTDOWN || system_state == MCE_SYSTEM_STATE_REBOOT ) ) { mce_log(LL_WARN, "reject display mode request at shutdown/reboot"); next_state = display_state_curr; } UPDATE: if( want_state != next_state ) { mce_log(LL_DEBUG, "requested: %s, granted: %s", display_state_repr(want_state), display_state_repr(next_state)); } /* Note: An attempt to keep the current state can lead into this * datapipe input filter returning transiend power up/down * or undefined states. These must be ignored at the datapipe * output handler display_state_request_pipe(). */ return GINT_TO_POINTER(next_state); } /** Handle display_state_request_pipe notifications * * This is where display state transition starts * * @param data Requested display_state_t (as void pointer) */ static void mdy_datapipe_display_state_req_cb(gconstpointer data) { display_state_t next_state = GPOINTER_TO_INT(data); switch( next_state ) { case MCE_DISPLAY_OFF: case MCE_DISPLAY_LPM_OFF: case MCE_DISPLAY_LPM_ON: case MCE_DISPLAY_DIM: case MCE_DISPLAY_ON: /* Feed valid stable states into the state machine */ mdy_stm_push_target_change(next_state); break; default: case MCE_DISPLAY_UNDEF: case MCE_DISPLAY_POWER_UP: case MCE_DISPLAY_POWER_DOWN: /* Ignore transient or otherwise invalid display states */ /* Any "no-change" transient state requests practically * have to be side effects of display state request * filtering - no need to make fuzz about them */ if( next_state == display_state_curr ) break; mce_log(LL_WARN, "%s is not valid target state; ignoring", display_state_repr(next_state)); break; } } /** Handle display_state_curr_pipe notifications * * This is where display state transition ends * * @param data The display state stored in a pointer */ static void mdy_datapipe_display_state_curr_cb(gconstpointer data) { display_state_t prev = display_state_curr; display_state_curr = GPOINTER_TO_INT(data); if( display_state_curr == prev ) goto EXIT; /* Entered (visible) LPM state -> schedule sanity check */ if( display_state_curr == MCE_DISPLAY_LPM_ON ) mdy_lpm_schedule_sanitize(); mdy_statistics_update(); mdy_blanking_pause_evaluate_allowed(); mdy_blanking_inhibit_schedule_broadcast(); EXIT: return; } /** Handle display_state_next_pipe notifications * * This is where display state transition begins * * @param data The display state stored in a pointer */ static void mdy_datapipe_display_state_next_cb(gconstpointer data) { display_state_t prev = display_state_next; display_state_next = GPOINTER_TO_INT(data); if( display_state_next == prev ) goto EXIT; /* Leaving LPM state -> sanity check no longer needed */ if( display_state_next != MCE_DISPLAY_LPM_ON && display_state_next != MCE_DISPLAY_LPM_OFF ) mdy_lpm_cancel_sanitize(); mdy_ui_dimming_rethink(); /* Start/stop orientation sensor */ mdy_orientation_sensor_rethink(); mdy_blanking_rethink_afterboot_delay(); mdy_blanking_pause_evaluate_allowed(); EXIT: return; } /** Keypad slide input state; assume closed */ static cover_state_t keyboard_slide_input_state = COVER_CLOSED; /** Change notifications from keyboard_slide_state_pipe */ static void mdy_datapipe_keyboard_slide_input_state_cb(gconstpointer const data) { cover_state_t prev = keyboard_slide_input_state; keyboard_slide_input_state = GPOINTER_TO_INT(data); if( keyboard_slide_input_state == COVER_UNDEF ) keyboard_slide_input_state = COVER_CLOSED; if( keyboard_slide_input_state == prev ) goto EXIT; mce_log(LL_DEVEL, "keyboard_slide_input_state = %s -> %s", cover_state_repr(prev), cover_state_repr(keyboard_slide_input_state)); /* force blanking reprogramming */ mdy_blanking_rethink_timers(true); EXIT: return; } /** Keypad available output state; assume unknown */ static cover_state_t keyboard_available_state = COVER_UNDEF; static void mdy_datapipe_keyboard_available_state_cb(gconstpointer const data) { cover_state_t prev = keyboard_available_state; keyboard_available_state = GPOINTER_TO_INT(data); if( keyboard_available_state == prev ) goto EXIT; mce_log(LL_DEBUG, "keyboard_available_state = %s -> %s", cover_state_repr(prev), cover_state_repr(keyboard_available_state)); /* force blanking reprogramming */ mdy_blanking_rethink_timers(true); EXIT: return; } /** Handle display_brightness_pipe notifications * * @note A brightness request is only sent if the value changed * @param data The display brightness stored in a pointer */ static void mdy_datapipe_display_brightness_cb(gconstpointer data) { static gint curr = -1; gint prev = curr; curr = GPOINTER_TO_INT(data); if( curr == prev ) goto EXIT; mce_log(LL_DEBUG, "brightness: %d -> %d", prev, curr); mdy_brightness_set_on_level(curr); EXIT: return; } /** Handle lpm_brightness_pipe notifications * * @note A brightness request is only sent if the value changed * * @param data The display brightness stored in a pointer */ static void mdy_datapipe_lpm_brightness_cb(gconstpointer data) { static gint curr = -1; gint prev = curr; curr = GPOINTER_TO_INT(data); mce_log(LL_DEBUG, "input: %d -> %d", prev, curr); if( curr == prev ) goto EXIT; mdy_brightness_set_lpm_level(curr); EXIT: return; } /* Cached audio routing state */ static audio_route_t audio_route = AUDIO_ROUTE_HANDSET; /** Handle audio_route_pipe notifications */ static void mdy_datapipe_audio_route_cb(gconstpointer data) { audio_route_t prev = audio_route; audio_route = GPOINTER_TO_INT(data); if( audio_route == prev ) goto EXIT; mce_log(LL_DEBUG, "audio_route: %s -> %s", audio_route_repr(prev), audio_route_repr(audio_route)); mdy_blanking_rethink_timers(false); EXIT: return; } /** Cached charger connection state */ static charger_state_t charger_state = CHARGER_STATE_UNDEF; /** Handle charger_state_pipe notifications * * @param data TRUE if the charger was connected, * FALSE if the charger was disconnected */ static void mdy_datapipe_charger_state_cb(gconstpointer data) { charger_state_t prev = charger_state; charger_state = GPOINTER_TO_INT(data); if( charger_state == prev ) goto EXIT; mce_log(LL_DEBUG, "charger_state: %s -> %s", charger_state_repr(prev), charger_state_repr(charger_state)); // keep display on/dim while charging etc mdy_blanking_rethink_timers(false); // charging can block suspend mdy_stm_schedule_rethink(); EXIT: return; } /** Handle uiexception_type_pipe notifications */ static void mdy_datapipe_uiexception_type_cb(gconstpointer data) { uiexception_type_t prev = uiexception_type; uiexception_type = GPOINTER_TO_INT(data); if( uiexception_type == prev ) goto EXIT; mce_log(LL_DEBUG, "uiexception_type = %s", uiexception_type_repr(uiexception_type)); // normal on->dim->blank might not be applicable mdy_blanking_rethink_timers(false); // notification exception state blocks suspend mdy_stm_schedule_rethink(); EXIT: return; } /** Cached alarm ui state */ static alarm_ui_state_t alarm_ui_state = MCE_ALARM_UI_INVALID_INT32; /** Handle alarm_ui_state_pipe notifications * * @param data Unused */ static void mdy_datapipe_alarm_ui_state_cb(gconstpointer data) { alarm_ui_state_t prev = alarm_ui_state; alarm_ui_state = GPOINTER_TO_INT(data); if( alarm_ui_state == prev ) goto EXIT; mce_log(LL_DEBUG, "alarm_ui_state: %s -> %s", alarm_state_repr(prev), alarm_state_repr(alarm_ui_state)); /* Act dead alarm ui does not implement compositor service. * Open/close fbdevice as if compositor were started/stopped * based on alarm ui state changes */ mdy_fbdev_rethink(); mdy_blanking_rethink_timers(false); // suspend policy mdy_stm_schedule_rethink(); EXIT: return; } /** Cached proximity sensor state */ static cover_state_t proximity_sensor_actual = COVER_UNDEF; /** Handle proximity_sensor_actual_pipe notifications * * @param data Unused */ static void mdy_datapipe_proximity_sensor_actual_cb(gconstpointer data) { cover_state_t prev = proximity_sensor_actual; proximity_sensor_actual = GPOINTER_TO_INT(data); if( proximity_sensor_actual == prev ) goto EXIT; mce_log(LL_DEBUG, "proximity_sensor_actual = %s", proximity_state_repr(proximity_sensor_actual)); /* handle toggling between LPM_ON and LPM_OFF */ mdy_blanking_rethink_proximity(); EXIT: return; } /** Cached power saving mode state */ static gboolean power_saving_mode_active = MCE_DEFAULT_EM_ENABLE_PSM; /** Handle power_saving_mode_active_pipe notifications * * @param data Unused */ static void mdy_datapipe_power_saving_mode_active_cb(gconstpointer data) { gboolean prev = power_saving_mode_active; power_saving_mode_active = GPOINTER_TO_INT(data); if( power_saving_mode_active == prev ) goto EXIT; mce_log(LL_DEBUG, "power_saving_mode_active = %d", power_saving_mode_active); /* Switch active brightness and CABC mode */ mdy_datapipe_execute_brightness(); mdy_cabc_mode_set(0); EXIT: return; } /** Cached call state */ static call_state_t call_state = CALL_STATE_INVALID; /** Handle call_state_pipe notifications * * @param data Unused */ static void mdy_datapipe_call_state_trigger_cb(gconstpointer data) { call_state_t prev = call_state; call_state = GPOINTER_TO_INT(data); if( call_state == prev ) goto EXIT; mce_log(LL_DEBUG, "call_state = %s", call_state_repr(call_state)); mdy_blanking_rethink_timers(false); // autosuspend policy mdy_callstate_set_changed(); EXIT: return; } /** Cached inactivity state */ static gboolean device_inactive = FALSE; /** Handle device_inactive_pipe notifications * * @param data The inactivity stored in a pointer; * TRUE if the device is inactive, * FALSE if the device is active */ static void mdy_datapipe_device_inactive_cb(gconstpointer data) { device_inactive = GPOINTER_TO_INT(data); /* while inactivity can be considered a "state", * activity is more like "event", i.e. it needs * to be handled without paying attention to * previous inactivity value */ mce_log(LL_DEBUG, "device_inactive = %d", device_inactive); if( device_inactive ) goto EXIT; /* Activity while adaptive dimming is primed causes * the next on->dim transition to use longer timeout */ mdy_blanking_trigger_adaptive_dimming(); switch( display_state_curr ) { case MCE_DISPLAY_ON: /* Explicitly reset the display dim timer */ mdy_blanking_rethink_timers(true); break; case MCE_DISPLAY_OFF: /* Activity alone will not make OFF->ON transition. */ break; case MCE_DISPLAY_DIM: /* DIM->ON on device activity */ mce_log(LL_NOTICE, "display on due to activity"); mce_datapipe_request_display_state(MCE_DISPLAY_ON); break; default: case MCE_DISPLAY_UNDEF: case MCE_DISPLAY_LPM_OFF: case MCE_DISPLAY_LPM_ON: case MCE_DISPLAY_POWER_UP: case MCE_DISPLAY_POWER_DOWN: break; } EXIT: return; } /** Cached Orientation Sensor value */ static orientation_state_t orientation_sensor_actual = MCE_ORIENTATION_UNDEFINED; /** Handle orientation_sensor_actual_pipe notifications * * @param data The orientation state stored in a pointer */ static void mdy_datapipe_orientation_sensor_actual_cb(gconstpointer data) { orientation_state_t prev = orientation_sensor_actual; orientation_sensor_actual = GPOINTER_TO_INT(data); if( orientation_sensor_actual == prev ) goto EXIT; mce_log(LL_DEBUG, "orientation_sensor_actual = %s", orientation_state_repr(orientation_sensor_actual)); /* Ignore sensor power up/down */ if( prev == MCE_ORIENTATION_UNDEFINED || orientation_sensor_actual == MCE_ORIENTATION_UNDEFINED ) goto EXIT; mdy_orientation_generate_activity(); EXIT: return; } /** React to shutdown-in-progress state changes */ static void mdy_datapipe_shutting_down_cb(gconstpointer aptr) { bool prev = mdy_shutdown_started_flag; mdy_shutdown_started_flag = GPOINTER_TO_INT(aptr); if( mdy_shutdown_started_flag == prev ) goto EXIT; if( mdy_shutdown_started_flag ) { mce_log(LL_DEBUG, "Shutdown started"); /* Cache start of shutdown time stamp */ mdy_shutdown_started_tick = mce_lib_get_boot_tick(); } else { mce_log(LL_DEBUG, "Shutdown canceled"); } /* Framebuffer must be kept open during shutdown */ mdy_fbdev_rethink(); EXIT: return; } /** Re-evaluate display brightness * * Should be called when display brigtness setting changes * and on entry to / exit from power save mode. */ static void mdy_datapipe_execute_brightness(void) { /* Choose between normal and PSM brightness */ int brightness = mdy_brightness_setting; if( power_saving_mode_active ) brightness = mce_xlat_int(1,100, 1,20, brightness); /* Then execute through the brightness pipe; to update * - mdy_brightness_level_display_on, and * - mdy_brightness_level_display_dim */ datapipe_exec_full(&display_brightness_pipe, GINT_TO_POINTER(brightness)); /* And through lpm brightness pipe; to update: * - mdy_brightness_level_display_lpm */ datapipe_exec_full(&lpm_brightness_pipe, GINT_TO_POINTER(brightness)); } /** Array of datapipe handlers */ static datapipe_handler_t mdy_datapipe_handlers[] = { // input triggers { .datapipe = &display_state_curr_pipe, .input_cb = mdy_datapipe_display_state_curr_cb, }, { .datapipe = &display_state_next_pipe, .input_cb = mdy_datapipe_display_state_next_cb, }, { .datapipe = &keyboard_slide_state_pipe, .input_cb = mdy_datapipe_keyboard_slide_input_state_cb, }, // input filters { .datapipe = &display_state_request_pipe, .filter_cb = mdy_datapipe_display_state_filter_cb, }, // output triggers { .datapipe = &keyboard_available_state_pipe, .output_cb = mdy_datapipe_keyboard_available_state_cb, }, { .datapipe = &display_state_request_pipe, .output_cb = mdy_datapipe_display_state_req_cb, }, { .datapipe = &display_brightness_pipe, .output_cb = mdy_datapipe_display_brightness_cb, }, { .datapipe = &lpm_brightness_pipe, .output_cb = mdy_datapipe_lpm_brightness_cb, }, { .datapipe = &charger_state_pipe, .output_cb = mdy_datapipe_charger_state_cb, }, { .datapipe = &system_state_pipe, .output_cb = mdy_datapipe_system_state_cb, }, { .datapipe = &orientation_sensor_actual_pipe, .output_cb = mdy_datapipe_orientation_sensor_actual_cb, }, { .datapipe = &submode_pipe, .output_cb = mdy_datapipe_submode_cb, }, { .datapipe = &interaction_expected_pipe, .output_cb = mdy_datapipe_interaction_expected_cb, }, { .datapipe = &device_inactive_pipe, .output_cb = mdy_datapipe_device_inactive_cb, }, { .datapipe = &call_state_pipe, .output_cb = mdy_datapipe_call_state_trigger_cb, }, { .datapipe = &power_saving_mode_active_pipe, .output_cb = mdy_datapipe_power_saving_mode_active_cb, }, { .datapipe = &proximity_sensor_actual_pipe, .output_cb = mdy_datapipe_proximity_sensor_actual_cb, }, { .datapipe = &alarm_ui_state_pipe, .output_cb = mdy_datapipe_alarm_ui_state_cb, }, { .datapipe = &uiexception_type_pipe, .output_cb = mdy_datapipe_uiexception_type_cb, }, { .datapipe = &audio_route_pipe, .output_cb = mdy_datapipe_audio_route_cb, }, { .datapipe = &packagekit_locked_pipe, .output_cb = mdy_datapipe_packagekit_locked_cb, }, { .datapipe = &compositor_service_state_pipe, .output_cb = mdy_datapipe_compositor_service_state_cb, }, { .datapipe = &lipstick_service_state_pipe, .output_cb = mdy_datapipe_lipstick_service_state_cb, }, { .datapipe = &lid_sensor_filtered_pipe, .output_cb = mdy_datapipe_lid_sensor_filtered_cb, }, { .datapipe = &shutting_down_pipe, .output_cb = mdy_datapipe_shutting_down_cb, }, // sentinel { .datapipe = 0, } }; static datapipe_bindings_t mdy_datapipe_bindings = { .module = "display", .handlers = mdy_datapipe_handlers, }; /** Append triggers/filters to datapipes */ static void mdy_datapipe_init(void) { mce_datapipe_init_bindings(&mdy_datapipe_bindings); } /** Remove triggers/filters from datapipes */ static void mdy_datapipe_quit(void) { mce_datapipe_quit_bindings(&mdy_datapipe_bindings); } /* ========================================================================= * * FBDEV_FD * ========================================================================= */ /** Decide whether frame buffer device should be kept open or not * * Having mce keep frame buffer device open during startup makes * it possible to make transition from boot logo to ui without * the display blanking in between. * * And similarly during shutdown/reboot the shutdown logo stays * visible after ui processes that drew it has been terminated. * * However we need to release the device file descriptor if ui * side happens to make unexpected exit, otherwise the stale * unresponsive ui would remain on screen. * * And act dead alarms are a special case, because there we have * ui that does compositor dbus inteface (act dead charging ui) * getting replaced with one that does not (act dead alarms ui). */ static void mdy_fbdev_rethink(void) { // have we seen compositor since mce startup static bool compositor_was_available = false; // assume framebuffer device should be kept open bool can_close = false; // do not close if compositor is up if( mdy_compositor_is_available() ) { compositor_was_available = true; goto EXIT; } // do not close if compositor has not yet been up if( !compositor_was_available ) goto EXIT; // do not close during shutdown if( mdy_shutdown_in_progress() ) goto EXIT; // do not close during os update if( mdy_osupdate_running ) goto EXIT; if( system_state == MCE_SYSTEM_STATE_ACTDEAD ) { // or when there are act dead alarms switch( alarm_ui_state ) { case MCE_ALARM_UI_RINGING_INT32: case MCE_ALARM_UI_VISIBLE_INT32: goto EXIT; default: break; } } else if( system_state != MCE_SYSTEM_STATE_USER ) { // or we are not in USER/ACT_DEAD goto EXIT; } // and since the whole close + reopen is needed only to // clear the display when something potentially has left // stale ui on screen - we skip it if the display is not // firmly in powered up state if( display_state_curr != display_state_next ) goto EXIT; if( !mdy_stm_display_state_needs_power(display_state_curr) ) goto EXIT; can_close = true; EXIT: if( can_close ) mce_fbdev_close(); else mce_fbdev_open(); } /* ========================================================================= * * LOW_POWER_MODE * ========================================================================= */ /** Predicate for: Entering LPM display states is allowed * * Entering LPM display states happens in co-operation between * mce and lipstick, via asynchronous D-Bus IPC. * * There are some situations where we know that entering LPM * just wont be possible - this function is used for detecting * such situations and denying attempts globally already at * display state request filtering stage. * * @see #mdy_datapipe_display_state_filter_cb() * * Additionally we can bump into situations where it looks like * entering LPM is ok, but UI side does not make the necessary * transitions - these are handled by starting a timer on entry * to MCE_DISPLAY_LPM_ON and checking relevant state data after * a brief delay (or cancelling the checkup upon leaving LPM). * * @see #mdy_datapipe_display_state_curr_cb() * @see #mdy_datapipe_display_state_next_cb() * * @return true if entering LPM states is allowed, false otherwise */ static bool mdy_lpm_allowed(void) { bool allowed = false; /* Feature must be enabled and supported */ if( !mdy_use_low_power_mode || !mdy_low_power_mode_supported ) goto EXIT; /* Lipstick must be running */ if( lipstick_service_state != SERVICE_STATE_RUNNING ) goto EXIT; /* Since call and alarm UIs are layered above lockscreen, * they are mutually exclusive with lpm */ if( uiexception_type & (UIEXCEPTION_TYPE_CALL | UIEXCEPTION_TYPE_ALARM) ) goto EXIT; allowed = true; EXIT: return allowed; } /** Timer id for: Sanity check after entering LPM state */ static guint mdy_lpm_sanitize_id = 0; /** Timer callback for: Sanity check after entering LPM state * * @param aptr unused user data pointer * * @return G_SOURCE_REMOVE (to stop timer from repeating) */ static gboolean mdy_lpm_sanitize_cb(gpointer aptr) { (void)aptr; mdy_lpm_sanitize_id = 0; /* If we're still in LPM state, and something is on * top of lockscreen -> we have dimmed out UI that * can't be interacted with -> exit from LPM. */ if( display_state_next != MCE_DISPLAY_LPM_ON && display_state_next != MCE_DISPLAY_LPM_OFF ) goto EXIT; if( topmost_window_pid == -1 ) goto EXIT; mce_log(LL_WARN, "app on screen; exiting lpm state"); mce_datapipe_request_display_state(MCE_DISPLAY_OFF); EXIT: return G_SOURCE_REMOVE; } /** Schedule sanity check for staying in LPM state */ static void mdy_lpm_schedule_sanitize(void) { if( !mdy_lpm_sanitize_id ) { mdy_lpm_sanitize_id = g_timeout_add(LPM_SANITIZE_DELAY, mdy_lpm_sanitize_cb, 0); } } /** Cancel scheduled LPM state sanity check */ static void mdy_lpm_cancel_sanitize(void) { if( mdy_lpm_sanitize_id ) { g_source_remove(mdy_lpm_sanitize_id), mdy_lpm_sanitize_id = 0; } } /* ========================================================================= * * HIGH_BRIGHTNESS_MODE * ========================================================================= */ /** Cached high brightness mode; this is the logical value */ static gint mdy_hbm_level_wanted = 0; /** High brightness mode; this is the last value written */ static gint mdy_hbm_level_written = -1; /** ID for high brightness mode timer source */ static guint mdy_hbm_timeout_cb_id = 0; /** Helper for updating high brightness state with bounds checking * * @param number high brightness mode [0 ... 2] */ static void mdy_hbm_set_level(int number) { int minval = 0; int maxval = 2; /* Clip value to valid range */ if( number < minval ) { mce_log(LL_ERR, "value=%d vs min=%d", number, minval); number = minval; } else if( number > maxval ) { mce_log(LL_ERR, "value=%d vs max=%d", number, maxval); number = maxval; } else mce_log(LL_DEBUG, "value=%d", number); /* Write unconditionally, but ... */ mce_write_number_string_to_file(&mdy_high_brightness_mode_output, number); /* ... make a note of the last value written */ mdy_hbm_level_written = number; } /** * Timeout callback for the high brightness mode * * @param data Unused * @return Always returns FALSE, to disable the timeout */ static gboolean mdy_hbm_timeout_cb(gpointer data) { (void)data; mce_log(LL_DEBUG, "HMB timer triggered"); mdy_hbm_timeout_cb_id = 0; /* Disable high brightness mode */ mdy_hbm_set_level(0); return FALSE; } /** * Cancel the high brightness mode timeout */ static void mdy_hbm_cancel_timeout(void) { /* Remove the timeout source for the high brightness mode */ if (mdy_hbm_timeout_cb_id != 0) { mce_log(LL_DEBUG, "HMB timer cancelled"); g_source_remove(mdy_hbm_timeout_cb_id); mdy_hbm_timeout_cb_id = 0; } } /** * Setup the high brightness mode timeout */ static void mdy_hbm_schedule_timeout(void) { gint timeout = DEFAULT_HBM_TIMEOUT; mdy_hbm_cancel_timeout(); /* Setup new timeout */ mce_log(LL_DEBUG, "HMB timer scheduled @ %d secs", timeout); mdy_hbm_timeout_cb_id = g_timeout_add_seconds(timeout, mdy_hbm_timeout_cb, NULL); } /** * Update high brightness mode * * @param hbm_level The wanted high brightness mode level; * will be overridden if the display is dimmed/off * or if high brightness mode is not supported */ static void mdy_hbm_rethink(void) { if (mdy_high_brightness_mode_supported == FALSE) goto EXIT; /* should not occur, but do nothing while in transition */ if( display_state_curr == MCE_DISPLAY_POWER_DOWN || display_state_curr == MCE_DISPLAY_POWER_UP ) { mce_log(LL_WARN, "hbm mode setting wile in transition"); goto EXIT; } /* If the display is off or dimmed, disable HBM */ if( display_state_curr != MCE_DISPLAY_ON ) { if (mdy_hbm_level_written != 0) { mdy_hbm_set_level(0); } } else if( mdy_hbm_level_written != mdy_hbm_level_wanted ) { mdy_hbm_set_level(mdy_hbm_level_wanted); } /** * Half brightness mode should be disabled after a certain timeout */ if( mdy_hbm_level_written <= 0 ) { mdy_hbm_cancel_timeout(); } else if( mdy_hbm_timeout_cb_id == 0 ) { mdy_hbm_schedule_timeout(); } EXIT: return; } /* ========================================================================= * * BACKLIGHT_BRIGHTNESS * ========================================================================= */ /** Maximum display brightness, hw specific */ static gint mdy_brightness_level_maximum = DEFAULT_MAXIMUM_DISPLAY_BRIGHTNESS; /** File used to get maximum display brightness */ static gchar *mdy_brightness_level_maximum_path = NULL; /** Cached brightness, last requested value; [0, mdy_brightness_level_maximum] */ static gint mdy_brightness_level_cached = -1; /** Cached brightness, last value accepted by kernel */ static gint mdy_brightness_level_active = -1; /** Brightness, when display is not off; [0, mdy_brightness_level_maximum] */ static gint mdy_brightness_level_display_on = 1; /** Dim brightness; [0, mdy_brightness_level_maximum] */ static gint mdy_brightness_level_display_dim = 1; /** LPM brightness; [0, mdy_brightness_level_maximum] */ static gint mdy_brightness_level_display_lpm = 1; /** Brightness to use on display wakeup; [0, mdy_brightness_level_maximum] */ static int mdy_brightness_level_display_resume = 1; /** File used to set display brightness */ static output_state_t mdy_brightness_level_output = { .path = NULL, .context = "brightness", .truncate_file = TRUE, .close_on_exit = FALSE, }; /** Hook for setting brightness * * Note: For use from mdy_brightness_set_level() only! * * @param number brightness value; after bounds checking */ static bool (*mdy_brightness_set_level_hook)(int number) = mdy_brightness_set_level_default; /** Number of consecutive brighness adjustment failures */ static unsigned mdy_brightness_failure_count = 0; /** Is hardware driven display fading supported */ static gboolean mdy_brightness_hw_fading_is_supported = FALSE; /** File used to set hw display fading */ static output_state_t mdy_brightness_hw_fading_output = { .path = NULL, .context = "hw_fading", .truncate_file = TRUE, .close_on_exit = TRUE, }; /** Brightness fade timeout callback ID */ static guint mdy_brightness_fade_timer_id = 0; /** Type of ongoing brightness fade */ static fader_type_t mdy_brightness_fade_type = FADER_IDLE; /** Monotonic time stamp for brightness fade start time */ static int64_t mdy_brightness_fade_start_time = 0; /** Monotonic time stamp for brightness fade end time */ static int64_t mdy_brightness_fade_end_time = 0; /** Brightness level at the start of brightness fade */ static int mdy_brightness_fade_start_level = 0; /** Brightness level at the end of brightness fade */ static int mdy_brightness_fade_end_level = 0; /** Default brightness fade length during display state transitions [ms] */ static gint mdy_brightness_fade_duration_def_ms = MCE_DEFAULT_BRIGHTNESS_FADE_DEFAULT_MS; static guint mdy_brightness_fade_duration_def_ms_setting_id = 0; /** Brightness fade length during display dimming [ms] */ static gint mdy_brightness_fade_duration_dim_ms = MCE_DEFAULT_BRIGHTNESS_FADE_DIMMING_MS; static guint mdy_brightness_fade_duration_dim_ms_setting_id = 0; /** Brightness fade length during ALS tuning [ms] */ static gint mdy_brightness_fade_duration_als_ms = MCE_DEFAULT_BRIGHTNESS_FADE_ALS_MS; static guint mdy_brightness_fade_duration_als_ms_setting_id = 0; /** Brightness fade length during display power down [ms] * * Note: Fade-to-black delays display power off and thus should be * kept short enough not to cause irritation (due to increased * response time to power key press). */ static gint mdy_brightness_fade_duration_blank_ms = MCE_DEFAULT_BRIGHTNESS_FADE_BLANK_MS; static guint mdy_brightness_fade_duration_blank_ms_setting_id = 0; /** Brightness fade length during display power up [ms] * * The fade in starts after frame buffer has been powered up. * However the brightness is acted on only after UI side draws * something on screen. Shortly after that there usually is a lot * of cpu activity and longer fade durations will stutter. * * 90 = 55 (fb wakeup -> 1st draw) + 35 (1st draw -> cpu load) * * Basically we end up seeing the brighter end of the fade in. */ static gint mdy_brightness_fade_duration_unblank_ms = MCE_DEFAULT_BRIGHTNESS_FADE_UNBLANK_MS; static guint mdy_brightness_fade_duration_unblank_ms_setting_id = 0; /** Use of orientation sensor enabled */ static gboolean mdy_orientation_sensor_enabled = MCE_DEFAULT_ORIENTATION_SENSOR_ENABLED; static guint mdy_orientation_sensor_enabled_setting_id = 0; /** Flipover gesture detection enabled */ static gboolean mdy_flipover_gesture_enabled = MCE_DEFAULT_FLIPOVER_GESTURE_ENABLED; static guint mdy_flipover_gesture_enabled_setting_id = 0; /** Orientation change generates activity enabled */ static gboolean mdy_orientation_change_is_activity = MCE_DEFAULT_ORIENTATION_CHANGE_IS_ACTIVITY; static guint mdy_orientation_change_is_activity_setting_id = 0; /** Timer for retrying brightness adjustment */ static guint mdy_brightness_retry_id = 0; /** Timer callback for retrying brightness adjustment * * @param aptr brightness value as void pointer * * @return G_SOURCE_REMOVE (to stop timer from repeating) */ static gboolean mdy_brightness_retry_cb(gpointer aptr) { int level = GPOINTER_TO_INT(aptr); mce_log(LL_WARN, "retry brightness adjustment: level=%d", level); mdy_brightness_retry_id = 0; mdy_brightness_set_level(level); return G_SOURCE_REMOVE; } /** Cancel pending brigthness adjustment retry */ static void mdy_brightness_cancel_retry(void) { if( mdy_brightness_retry_id ) { g_source_remove(mdy_brightness_retry_id), mdy_brightness_retry_id = 0; } } /** Schedule brigthness adjustment retry * * @param level brightness value to retry */ static void mdy_brightness_schedule_retry(int level) { mdy_brightness_cancel_retry(); mdy_brightness_retry_id = g_timeout_add(1000, mdy_brightness_retry_cb, GINT_TO_POINTER(level)); } /** Set display brightness via sysfs write */ static bool mdy_brightness_set_level_default(int number) { if( !mdy_brightness_level_output.path ) { /* Pretend success if we have nowhere to write to, * so that we do not trigger useless logging. */ return true; } return mce_write_number_string_to_file(&mdy_brightness_level_output, number); } #ifdef ENABLE_HYBRIS /** Set display brightness via libhybris */ static bool mdy_brightness_set_level_hybris(int number) { return mce_hybris_backlight_set_brightness(number); } #endif /** Helper for normalizing brightness to supported range * * @param number brightness value * * @return brightness value capped to supported range */ static int mdy_brightness_normalize_level(int number) { if( number < 0 ) number = 0; else if (number > mdy_brightness_level_maximum ) number = mdy_brightness_level_maximum; return number; } /** Helper for updating backlight brightness with bounds checking * * @param number brightness in 0 to mdy_brightness_level_maximum range */ static void mdy_brightness_set_level(int number) { /* If we manage to get out of hw bounds values from depths * of pipelines and state machines we could end up with * black screen without easy way out -> clip to valid range */ int value = mdy_brightness_normalize_level(number); if( value != number ) mce_log(LL_WARN, "out of bounds brightness level: %d -> %d", number, value); /* Cancel pending retry attempts */ mdy_brightness_cancel_retry(); /* Mark down what we wanted the brightess to be */ mdy_brightness_level_cached = value; /* Make an attempt to change actual brightness */ if( mdy_brightness_level_active != value ) { if( mdy_brightness_set_level_hook(value) ) { /* Successfully adjusted */ if( mdy_brightness_failure_count ) { mce_log(LL_WARN, "active brightness: %d -> %d (success after %u failures)", mdy_brightness_level_active, value, mdy_brightness_failure_count); mdy_brightness_failure_count = 0; } else { mce_log(LL_DEBUG, "active brightness: %d -> %d", mdy_brightness_level_active, value ); } /* Mark down: In sync with kernel */ mdy_brightness_level_active = value; } else { /* Adjustment failed */ mdy_brightness_failure_count += 1; /* As we more or less expect to get some amount of * failures during bootup and compositor switchovers, * make an attempt to limit diagnostic noise when mce * is running under normal verbosity. */ int verbosity = LL_WARN; if( value <= 0 || mdy_brightness_failure_count % 10 ) verbosity = LL_DEBUG; mce_log(verbosity, "active brightness: %d -> %d (failure count %u)", mdy_brightness_level_active, value, mdy_brightness_failure_count); /* Mark down: Out of sync with kernel */ mdy_brightness_level_active = -1; if( mdy_compositor_is_enabled() && value > 0 ) { /* There seems to be a timing hazard regarding * dri compositor unblank vs when brightness * adjustments via sysfs will be accepted by * kernel. At the moment we do not have suitable * ipc mechanisms in place to properly deal with * this -> as a workaround: retry periodically * until it succeeds ... */ mdy_brightness_schedule_retry(value); } } } } /** Helper for flushing cached backlight brightness value */ static void mdy_brightness_forget_level(void) { if( mdy_brightness_level_active != -1 ) { mdy_brightness_level_active = -1; mce_log(LL_DEBUG, "active brightness: %d", mdy_brightness_level_active); } } /** Helper for boosting mce scheduling priority during brightness fading * * Any scheduling hiccups during backlight brightness tuning are really * visible. To make it less likely to occur, this function is used to * move mce process to SCHED_FIFO while fade timer is active. * * Note: Currently this is the only place where mce needs to tweak * the scheduling parameters. If that ever changes, a better interface * needs to be built. */ static void mdy_brightness_set_priority_boost(bool enable) { /* Initialize cached state to default scheduler */ static int normal_scheduler = SCHED_OTHER; static int normal_priority = 0; /* Initially boosted priority is not in use */ static bool is_enabled = false; if( is_enabled == enable ) goto EXIT; int scheduler = SCHED_OTHER; struct sched_param param; memset(¶m, 0, sizeof param); if( enable ) { /* Cache current scheduling parameters */ if( (scheduler = sched_getscheduler(0)) == -1 ) mce_log(LL_WARN, "sched_getscheduler: %m"); else if( sched_getparam(0, ¶m) == -1 ) mce_log(LL_WARN, "sched_getparam: %m"); else { normal_scheduler = scheduler; normal_priority = param.sched_priority; } /* Switch to medium priority fifo scheduling */ scheduler = SCHED_FIFO; param.sched_priority = (sched_get_priority_min(scheduler) + sched_get_priority_max(scheduler)) / 2; } else { /* Switch back to cached scheduling parameters */ scheduler = normal_scheduler; param.sched_priority = normal_priority; } mce_log(LL_DEBUG, "sched=%d, prio=%d", scheduler, param.sched_priority); if( sched_setscheduler(0, scheduler, ¶m) == -1 ) { static bool warned = false; if( !warned ) { warned = true; mce_log(LL_WARN, "can't %s high priority mode: %m", enable ? "enter" : "leave"); } } /* The logical change is made even if we fail to actually change * the scheduling parameters */ is_enabled = enable; EXIT: return; } /** Helper for cancelling brightness fade and forcing a brightness level * * @param number brightness in 0 to mdy_brightness_level_maximum range */ static void mdy_brightness_force_level(int number) { mce_log(LL_DEBUG, "brightness from %d to %d", mdy_brightness_level_cached, number); mdy_brightness_stop_fade_timer(); mdy_brightness_fade_start_level = mdy_brightness_fade_end_level = number; mdy_brightness_fade_start_time = mdy_brightness_fade_end_time = mce_lib_get_boot_tick(); mdy_brightness_set_level(number); } /** Helper for re-evaluating need for als-tuning * * Ongoing higher priority fade animations can block als tuning * from taking place. This function is used to re-evaluates the * need for als tuning once the blocking transitions finish. * * @param fade_type type of fade animation that was just finished */ static void mdy_brightness_fade_continue_with_als(fader_type_t fader_type) { /* Only applicable after ending fade animations that * temporarily block als transitions - keep this in * sync with logic in mdy_brightness_is_fade_allowed(). */ switch( fader_type ) { case FADER_DEFAULT: case FADER_DIMMING: break; default: goto EXIT; } /* The display state must be stable too */ if( display_state_next != display_state_curr ) goto EXIT; /* Target level depends on the display state */ int level = mdy_brightness_fade_end_level; switch( display_state_curr ) { case MCE_DISPLAY_LPM_ON: level = mdy_brightness_level_display_lpm; break; case MCE_DISPLAY_DIM: level = mdy_brightness_level_display_dim; break; case MCE_DISPLAY_ON: level = mdy_brightness_level_display_on; break; default: goto EXIT; } /* Apply if change is needed */ if( level != mdy_brightness_fade_end_level ) { mce_log(LL_DEBUG, "continue with als tuning"); mdy_brightness_set_fade_target_als(level); } EXIT: return; } /** * Timeout callback for the brightness fade * * @param data Unused * @return Returns TRUE to repeat, until the cached brightness has reached * the destination value; when this happens, FALSE is returned */ static gboolean mdy_brightness_fade_timer_cb(gpointer data) { (void)data; gboolean keep_going = FALSE; if( !mdy_brightness_fade_timer_id ) goto EXIT; /* Assume end of transition brightness is to be used */ int lev = mdy_brightness_fade_end_level; /* Get current time */ int64_t now = mce_lib_get_boot_tick(); if( mdy_brightness_fade_start_time <= now && now < mdy_brightness_fade_end_time ) { /* Linear interpolation */ int weight_end = (int)(now - mdy_brightness_fade_start_time); int weight_beg = (int)(mdy_brightness_fade_end_time - now); int weight_tot = weight_end + weight_beg; lev = (weight_end * mdy_brightness_fade_end_level + weight_beg * mdy_brightness_fade_start_level + weight_tot/2) / weight_tot; keep_going = TRUE; } mdy_brightness_set_level(lev); /* Cleanup if finished */ if( !keep_going ) { /* Cache fade type that just finished */ fader_type_t fader_type = mdy_brightness_fade_type; /* Reset fader state */ mdy_brightness_fade_timer_id = 0; mdy_brightness_cleanup_fade_timer(); mce_log(LL_DEBUG, "fader finished"); /* Check if we need to continue with als tuning */ mdy_brightness_fade_continue_with_als(fader_type); } EXIT: return keep_going; } /** Helper function for cleaning up brightness fade timer * * Common fader timer cancellation logic * * NOTE: For use from mdy_brightness_fade_timer_cb() and * mdy_brightness_stop_fade_timer() functions only. */ static void mdy_brightness_cleanup_fade_timer(void) { /* Remove timer source */ if( mdy_brightness_fade_timer_id ) g_source_remove(mdy_brightness_fade_timer_id), mdy_brightness_fade_timer_id = 0; /* Clear ongoing fade type */ mdy_brightness_fade_type = FADER_IDLE; /* Unblock display off transition */ mdy_stm_schedule_rethink(); /* Cancel scheduling priority boost */ mdy_brightness_set_priority_boost(false); } /** * Cancel the brightness fade timeout */ static void mdy_brightness_stop_fade_timer(void) { /* Cleanup if timer is active */ if( mdy_brightness_fade_timer_id ) mdy_brightness_cleanup_fade_timer(); } /** * Setup the brightness fade timeout * * @param step_time The time between each brightness step */ static void mdy_brightness_start_fade_timer(fader_type_t type, gint step_time) { if( !mdy_brightness_fade_timer_id ) { mce_log(LL_DEBUG, "fader started"); mdy_brightness_set_priority_boost(true); } else { mce_log(LL_DEBUG, "fader restarted"); g_source_remove(mdy_brightness_fade_timer_id), mdy_brightness_fade_timer_id = 0; } /* Setup new timeout */ mdy_brightness_fade_timer_id = g_timeout_add(step_time, mdy_brightness_fade_timer_cb, NULL); /* Set ongoing fade type */ mdy_brightness_fade_type = type; } static bool mdy_brightness_fade_is_active(void) { return mdy_brightness_fade_timer_id != 0; } /** Check if starting brightness fade of given type is allowed * * @param type fade type to start * * @return true if the fading can start, false otherwise */ static bool mdy_brightness_is_fade_allowed(fader_type_t type) { bool allowed = true; switch( mdy_brightness_fade_type ) { default: case FADER_IDLE: case FADER_ALS: break; case FADER_DEFAULT: case FADER_DIMMING: /* Deny als tuning during display state transitions. * * The idea here is that fades associated with display * state transitions are kept as uniform as possible * even if lightning conditions change during the * fade. * * After these blocking fade animations are finished, * mdy_brightness_fade_continue_with_als() is used * to re-evaluate the need for possible als tuning * again. */ if( type == FADER_ALS ) allowed = false; break; case FADER_BLANK: /* ongoing fade to black can't be cancelled */ allowed = false; break; case FADER_UNBLANK: /* only unblank target level can be changed */ if( type != FADER_UNBLANK ) allowed = false; break; } return allowed; } /** * Update brightness fade * * Will fade from current value to new value * * @param new_brightness The new brightness to fade to */ static void mdy_brightness_set_fade_target_ex(fader_type_t type, gint new_brightness, gint transition_time) { /* While something like 20-40 ms would suffice for most cases * using smaller 4 ms value allows us to make few steps during * the short time window we have available during unblanking. */ const int delay_min = 4; /* Negative transition time: constant velocity change [%/s] */ if( transition_time < 0 ) { int d = abs(new_brightness - mdy_brightness_level_cached); // velocity: percent/sec -> steps/sec int v = mce_xlat_int(1, 100, 1, mdy_brightness_level_maximum, -transition_time); if( v <= 0 ) transition_time = MCE_FADER_DURATION_HW_MAX; else transition_time = (1000 * d + v/2) / v; } /* Keep transition time in sane range */ transition_time = mce_clip_int(MCE_FADER_DURATION_HW_MIN, MCE_FADER_DURATION_HW_MAX, transition_time); mce_log(LL_DEBUG, "type %s fade from %d to %d in %d ms", fader_type_name(type), mdy_brightness_level_cached, new_brightness, transition_time); if( !mdy_brightness_is_fade_allowed(type) ) { mce_log(LL_DEBUG, "ignoring fade=%s; ongoing fade=%s", fader_type_name(type), fader_type_name(mdy_brightness_fade_type)); goto EXIT; } /* If we're already at the target level, stop any * ongoing fading activity */ if( mdy_brightness_level_cached == mdy_brightness_level_active && mdy_brightness_level_cached == new_brightness ) { mdy_brightness_stop_fade_timer(); goto EXIT; } /* Small enough changes are made immediately instead of * using fading timer */ if( abs(mdy_brightness_level_cached - new_brightness) <= 1 ) { mce_log(LL_DEBUG, "small change; not using fader"); mdy_brightness_force_level(new_brightness); goto EXIT; } /* Calculate fading time window */ int64_t beg = mce_lib_get_boot_tick(); int64_t end = beg + transition_time; /* If an ongoing fading has the same target level and it * will finish before the new one would, use it */ if( mdy_brightness_fade_is_active() && mdy_brightness_fade_end_level == new_brightness && mdy_brightness_fade_end_time <= end ) goto EXIT; /* Move fading start point to current time */ mdy_brightness_fade_start_time = beg; if( mdy_brightness_fade_end_time <= beg ) { /* Previous fading has ended -> set fading end point */ mdy_brightness_fade_end_time = end; } else if( mdy_brightness_fade_end_time > end ) { /* Current fading would end later -> adjust end point */ mdy_brightness_fade_end_time = end; } /* Set up fade start and end brightness levels */ mdy_brightness_fade_start_level = mdy_brightness_normalize_level(mdy_brightness_level_cached); mdy_brightness_fade_end_level = mdy_brightness_normalize_level(new_brightness); /* If the - possibly adjusted - transition time is so short that * only couple of adjustments would be made, do an immediate * level set instead of fading */ transition_time = (int)(mdy_brightness_fade_end_time - mdy_brightness_fade_start_time); if( transition_time < delay_min * 3 ) { mce_log(LL_DEBUG, "short transition; not using fader"); mdy_brightness_force_level(new_brightness); goto EXIT; } /* Calculate desired brightness change velocity. */ int steps = abs(mdy_brightness_fade_end_level - mdy_brightness_fade_start_level); int delay = transition_time / steps; // NB steps != 0 /* Reject insane timer wakeup frequencies. The fade timer * utilizes timestamp based interpolation, so the delay * does not need to be exactly as planned above. */ if( delay < delay_min ) delay = delay_min; mdy_brightness_start_fade_timer(type, delay); EXIT: return; } /** Start brightness fading associated with display state change */ static void mdy_brightness_set_fade_target_default(gint new_brightness) { mdy_brightness_set_fade_target_ex(FADER_DEFAULT, new_brightness, mdy_brightness_fade_duration_def_ms); } /** Start brightness fading after powering up the display */ static void mdy_brightness_set_fade_target_unblank(gint new_brightness) { mdy_brightness_set_fade_target_ex(FADER_UNBLANK, new_brightness, mdy_brightness_fade_duration_unblank_ms); } /** Start fade to black before powering off the display */ static void mdy_brightness_set_fade_target_blank(void) { if( call_state == CALL_STATE_ACTIVE ) { /* Unlike the other brightness fadings, the fade-to-black blocks * the display state machine and thus delays the whole display * power off sequence. * * Thus it must not be used during active call to avoid stray * touch input from ear/chin when proximity blanking is in use. */ mdy_brightness_force_level(0); goto EXIT; } mdy_brightness_set_fade_target_ex(FADER_BLANK, 0, mdy_brightness_fade_duration_blank_ms); EXIT: return; } /** Start brightness fading associated with display dimmed state */ static void mdy_brightness_set_fade_target_dimming(gint new_brightness) { mdy_brightness_set_fade_target_ex(FADER_DIMMING, new_brightness, mdy_brightness_fade_duration_dim_ms); } /** Flag for: Automatic ALS based brightness tuning is allowed */ bool mdy_brightness_als_fade_allowed = false; /** Start brightness fading due to ALS / brightness setting change */ static void mdy_brightness_set_fade_target_als(gint new_brightness) { /* Update wake up brightness level in case we got als data * before unblank fading has been started */ mce_log(LL_DEBUG, "resume level: %d -> %d", mdy_brightness_level_display_resume, new_brightness); mdy_brightness_level_display_resume = new_brightness; /* If currently unblanking, just adjust the target level */ if( mdy_brightness_fade_type == FADER_UNBLANK ) { mdy_brightness_set_fade_target_unblank(new_brightness); mce_log(LL_DEBUG, "skip als fade; adjust unblank target"); goto EXIT; } /* Check if main display state machine is blocking ALS tuning */ if( !mdy_brightness_als_fade_allowed ) { mce_log(LL_DEBUG, "skip als fade; not allowed"); goto EXIT; } /* Assume configured fade duration is used */ int dur = mdy_brightness_fade_duration_als_ms; /* To make effects of changing the brightness settings * more clear, override constant time / long als fade durations * that happen immediately after relevant settings changes. */ if( dur < 0 || dur > MCE_FADER_DURATION_SETTINGS_CHANGED ) { int64_t now = mce_lib_get_boot_tick(); int64_t end = (mdy_brightness_setting_change_time + MCE_FADER_DURATION_SETTINGS_CHANGED); if( now <= end ) dur = MCE_FADER_DURATION_SETTINGS_CHANGED; } /* Start als brightness fade */ mdy_brightness_set_fade_target_ex(FADER_ALS, new_brightness, dur); EXIT: return; } /** Get static display brightness setting in hw units */ static int mdy_brightness_get_dim_static(void) { // N % of hw maximum return mce_xlat_int(1, 100, 1, mdy_brightness_level_maximum, mdy_brightness_dim_static); } /** Get dynamic display brightness setting in hw units */ static int mdy_brightness_get_dim_dynamic(void) { // N % of display on brightness return mce_xlat_int(1, 100, 1, mdy_brightness_level_display_on, mdy_brightness_dim_dynamic); } /** Get start of compositor dimming threshold in hw units */ static int mdy_brightness_get_dim_threshold_lo(void) { // N % of hw maximum return mce_xlat_int(1, 100, 1, mdy_brightness_level_maximum, mdy_brightness_dim_compositor_lo); } /** Get maximal compositor dimming threshold in hw units */ static int mdy_brightness_get_dim_threshold_hi(void) { // N % of hw maximum return mce_xlat_int(1, 100, 1, mdy_brightness_level_maximum, mdy_brightness_dim_compositor_hi); } static void mdy_brightness_set_dim_level(void) { /* Update backlight level to use in dimmed state */ int brightness = mdy_brightness_get_dim_static(); int dynamic = mdy_brightness_get_dim_dynamic(); if( brightness > dynamic ) brightness = dynamic; if( mdy_brightness_level_display_dim != brightness ) { mce_log(LL_DEBUG, "brightness.dim: %d -> %d", mdy_brightness_level_display_dim, brightness); mdy_brightness_level_display_dim = brightness; } /* Check if compositor side fading needs to be used */ int difference = (mdy_brightness_level_display_on - mdy_brightness_level_display_dim); /* Difference level where minimal compositor fading starts */ int threshold_lo = mdy_brightness_get_dim_threshold_lo(); /* Difference level where maximal compositor fading is reached */ int threshold_hi = mdy_brightness_get_dim_threshold_hi(); /* If fading start is set beyond the point where maximal fading * is reached, use on/off control at high threshold point */ if( threshold_lo <= threshold_hi ) threshold_lo = threshold_hi + 1; int compositor_fade_level = xlat(threshold_hi, threshold_lo, MCE_FADER_MAXIMUM_OPACITY_PERCENT, 0, difference); /* Note: The pattern can be activated anytime, it will get * effective only when display is in dimmed state * * FIXME: When ui side dimming is working, the led pattern * hack should be removed altogether. */ datapipe_exec_full(compositor_fade_level > 0 ? &led_pattern_activate_pipe : &led_pattern_deactivate_pipe, MCE_LED_PATTERN_DISPLAY_DIMMED); /* Update ui side fader opacity value */ mdy_ui_dimming_set_level(compositor_fade_level); } static void mdy_brightness_set_lpm_level(gint level) { /* Map from: 1-100% to: 1-hw_max */ int brightness = mce_xlat_int(1, 100, 1, mdy_brightness_level_maximum, level); mce_log(LL_DEBUG, "mdy_brightness_level_display_lpm: %d -> %d", mdy_brightness_level_display_lpm, brightness); mdy_brightness_level_display_lpm = brightness; /* Take updated values in use - based on non-transitional * display state we are in or transitioning to */ switch( display_state_next ) { case MCE_DISPLAY_LPM_ON: mdy_brightness_set_fade_target_als(mdy_brightness_level_display_lpm); break; default: case MCE_DISPLAY_OFF: case MCE_DISPLAY_LPM_OFF: case MCE_DISPLAY_DIM: case MCE_DISPLAY_ON: case MCE_DISPLAY_UNDEF: case MCE_DISPLAY_POWER_DOWN: case MCE_DISPLAY_POWER_UP: break; } return; } static void mdy_brightness_set_on_level(gint hbm_and_level) { gint new_brightness = (hbm_and_level >> 0) & 0xff; gint new_hbm_level = (hbm_and_level >> 8) & 0xff; mce_log(LL_INFO, "hbm_level=%d, brightness=%d", new_hbm_level, new_brightness); /* If the pipe is choked, ignore the value */ if (new_brightness == 0) goto EXIT; /* This is always necessary, * since 100% + HBM is not the same as 100% without HBM */ mdy_hbm_level_wanted = new_hbm_level; mdy_hbm_rethink(); /* Adjust the value, since it's a percentage value, and filter out * the high brightness setting */ new_brightness = (mdy_brightness_level_maximum * new_brightness) / 100; /* The value we have here is for non-dimmed screen only */ if( mdy_brightness_level_display_on != new_brightness ) { mce_log(LL_DEBUG, "brightness.on: %d -> %d", mdy_brightness_level_display_on, new_brightness); mdy_brightness_level_display_on = new_brightness; } /* Re-evaluate dim brightness too */ mdy_brightness_set_dim_level(); /* Note: The lpm brightness is handled separately */ /* Take updated values in use - based on non-transitional * display state we are in or transitioning to */ switch( display_state_next ) { case MCE_DISPLAY_OFF: case MCE_DISPLAY_LPM_OFF: case MCE_DISPLAY_LPM_ON: break; case MCE_DISPLAY_DIM: mdy_brightness_set_fade_target_als(mdy_brightness_level_display_dim); break; default: case MCE_DISPLAY_POWER_DOWN: case MCE_DISPLAY_POWER_UP: case MCE_DISPLAY_UNDEF: break; case MCE_DISPLAY_ON: mdy_brightness_set_fade_target_als(mdy_brightness_level_display_on); break; } EXIT: return; } /* ========================================================================= * * UI_SIDE_DIMMING * ========================================================================= */ /** Compositor side fade to black opacity level percentage * * Used when backlight brightness alone can't produce visible dimmed state */ static int mdy_ui_dimming_level = 0; /** Update mdy_ui_dimming_level state */ static void mdy_ui_dimming_set_level(int level) { mdy_ui_dimming_level = level; mdy_ui_dimming_rethink(); } /** Re-evaluate target opacity for ui side dimming * * Should be called when: * 1. on/dimmed brightness changes * 2. display state transition starts * 3. display state transition is finished */ static void mdy_ui_dimming_rethink(void) { /* Initialize previous value to invalid state so that initial * evaluation on mce startup forces signal to be sent */ static dbus_int32_t dimming_prev = -1; /* This gets a bit hairy because we do not want to restart the * ui side fade animation once it has started and heading to * the correct level -> on display power up we want to make * only one guess when/if the fading target changes and how * fast the change should happen. * * The triggers for calling this function are: * 1) display state transition starts * 2) als tuning changes mdy_ui_dimming_level * * When (1) happens, both display_state_curr and display_state_next * hold stable states. * * If (2) happens during display power up/down, the * display_state_curr variable can hold transitient * MCE_DISPLAY_POWER_UP/DOWN states. */ /* Assume that ui side dimming should not occur */ dbus_int32_t dimming_curr = 0; if( display_state_curr == MCE_DISPLAY_POWER_DOWN || display_state_next == MCE_DISPLAY_OFF || display_state_next == MCE_DISPLAY_LPM_OFF ) { /* At or entering powered off state -> keep current state */ if( dimming_prev >= 0 ) dimming_curr = dimming_prev; } else if( display_state_next == MCE_DISPLAY_DIM ) { /* At or entering dimmed state -> use if needed */ dimming_curr = mdy_ui_dimming_level; } /* Skip the rest if the target level does not change */ if( dimming_prev == dimming_curr ) goto EXIT; dimming_prev = dimming_curr; /* Assume the change is due to ALS tuning */ dbus_int32_t duration = mdy_brightness_fade_duration_als_ms; if( display_state_curr == MCE_DISPLAY_POWER_UP ) { /* Leaving powered off state -> use unblank duration */ duration = mdy_brightness_fade_duration_unblank_ms; } else if( display_state_curr == MCE_DISPLAY_POWER_DOWN ) { /* Entering powered off state -> use blank duration */ duration = mdy_brightness_fade_duration_blank_ms; } else if( display_state_curr != display_state_next ) { /* Ongoing display state transition that does not need * or has not yet entered transient state */ if( display_state_curr == MCE_DISPLAY_OFF || display_state_curr == MCE_DISPLAY_LPM_OFF ) { /* Leaving powered off state -> use unblank duration */ duration = mdy_brightness_fade_duration_unblank_ms; } else if( display_state_next == MCE_DISPLAY_OFF || display_state_next == MCE_DISPLAY_LPM_OFF ) { /* Entering powered off state -> use blank duration */ duration = mdy_brightness_fade_duration_blank_ms; } else if( display_state_next == MCE_DISPLAY_DIM ) { /* Entering dimmed state -> use dimming duration */ duration = mdy_brightness_fade_duration_dim_ms; } else { /* Use default state transition duration */ duration = mdy_brightness_fade_duration_def_ms; } } /* Keep transition time in sane range. Also takes care * that negative values used to signify constant velocity * change do not get passed to compositor side dimming. */ duration = mce_clip_int(MCE_FADER_DURATION_UI_MIN, MCE_FADER_DURATION_UI_MAX, duration); mce_log(LL_DEVEL, "sending dbus signal: %s %d %d", MCE_FADER_OPACITY_SIG, dimming_curr, duration); dbus_send(0, MCE_SIGNAL_PATH, MCE_SIGNAL_IF, MCE_FADER_OPACITY_SIG, 0, DBUS_TYPE_INT32, &dimming_curr, DBUS_TYPE_INT32, &duration, DBUS_TYPE_INVALID); EXIT: return; } /* ========================================================================= * * CONTENT_ADAPTIVE_BACKLIGHT_CONTROL * ========================================================================= */ /** * CABC mappings; D-Bus API modes vs SysFS mode */ cabc_mode_mapping_t mdy_cabc_mode_mapping[] = { { .dbus = MCE_CABC_MODE_OFF, .sysfs = CABC_MODE_OFF, .available = FALSE }, { .dbus = MCE_CABC_MODE_UI, .sysfs = CABC_MODE_UI, .available = FALSE }, { .dbus = MCE_CABC_MODE_STILL_IMAGE, .sysfs = CABC_MODE_STILL_IMAGE, .available = FALSE }, { .dbus = MCE_CABC_MODE_MOVING_IMAGE, .sysfs = CABC_MODE_MOVING_IMAGE, .available = FALSE }, { .dbus = NULL, .sysfs = NULL, .available = FALSE } }; /** * Set CABC mode * * @param mode The CABC mode to set */ static void mdy_cabc_mode_set(const gchar *mode) { static bool available_modes_scanned = false; if( !mdy_cabc_is_supported ) goto EXIT; if( !mdy_cabc_available_modes_file ) goto EXIT; /* Update the list of available modes against the list we support */ if( !available_modes_scanned ) { available_modes_scanned = true; gchar *available_modes = NULL; if( !mce_read_string_from_file(mdy_cabc_available_modes_file, &available_modes) ) { goto EXIT; } for( size_t i = 0; mdy_cabc_mode_mapping[i].sysfs; ++i ) { if( strstr_delim(available_modes, mdy_cabc_mode_mapping[i].sysfs, " ") ) mdy_cabc_mode_mapping[i].available = TRUE; } g_free(available_modes); } if( !mode ) { if( power_saving_mode_active ) { mode = mdy_cabc_mode_psm ?: DEFAULT_PSM_CABC_MODE; } else { mode = mdy_cabc_mode_def ?: DEFAULT_CABC_MODE; } } /* If the requested mode is supported, use it */ for( size_t i = 0; mdy_cabc_mode_mapping[i].sysfs; ++i ) { if( !mdy_cabc_mode_mapping[i].available ) continue; /* Note: The value in the array is a static const string */ const char *have = mdy_cabc_mode_mapping[i].sysfs; if( strcmp(have, mode) ) continue; mce_write_string_to_file(mdy_cabc_mode_file, have); if( power_saving_mode_active ) mdy_cabc_mode_psm = have; else mdy_cabc_mode_def = have; break; } EXIT: return; } /* ========================================================================= * * BOOTUP_LED_PATTERN * ========================================================================= */ /** Re-evaluate whether we want POWER_ON led pattern or not */ static void mdy_poweron_led_rethink(void) { bool want_led = (mdy_init_done != TRISTATE_TRUE && mdy_bootstate == BOOTSTATE_USER); mce_log(LL_DEBUG, "%s MCE_LED_PATTERN_POWER_ON", want_led ? "activate" : "deactivate"); datapipe_exec_full(want_led ? &led_pattern_activate_pipe : &led_pattern_deactivate_pipe, MCE_LED_PATTERN_POWER_ON); } /** Timer id for delayed POWER_ON led state evaluation */ static guint mdy_poweron_led_rethink_id = 0; /** Timer callback for delayed POWER_ON led state evaluation */ static gboolean mdy_poweron_led_rethink_cb(gpointer aptr) { (void)aptr; if( mdy_poweron_led_rethink_id ) { mdy_poweron_led_rethink_id = 0; mdy_poweron_led_rethink(); } return FALSE; } /** Cancel delayed POWER_ON led state evaluation */ static void mdy_poweron_led_rethink_cancel(void) { if( mdy_poweron_led_rethink_id ) g_source_remove(mdy_poweron_led_rethink_id), mdy_poweron_led_rethink_id = 0; } /** Schedule delayed POWER_ON led state evaluation */ static void mdy_poweron_led_rethink_schedule(void) { if( !mdy_poweron_led_rethink_id ) mdy_poweron_led_rethink_id = g_idle_add(mdy_poweron_led_rethink_cb, 0); } /* ========================================================================= * * AUTOMATIC_BLANKING * ========================================================================= */ /** Check whether blanking rules for lockscreen should be used * * @return true if lockscreen blanking should be used, false otherwise */ static bool mdy_blanking_from_lockscreen(void) { return (submode & MCE_SUBMODE_TKLOCK) && !interaction_expected; } /** Re-calculate inactivity timeout * * This function should be called whenever the variables used * in the calculation are changed. */ static void mdy_blanking_update_device_inactive_delay(void) { /* Inactivity should be signaled around the time when the display * should have dimmed and blanked - even if the actual blanking is * blocked by blanking pause and/or blanking inhibit mode. */ gint prev = datapipe_get_gint(inactivity_delay_pipe); gint curr = (mdy_blanking_get_default_dimming_delay() + mdy_blank_timeout); if( prev == curr ) goto EXIT; mce_log(LL_DEBUG, "device_inactive_delay = %d", curr); datapipe_exec_full(&inactivity_delay_pipe, GINT_TO_POINTER(curr)); EXIT: return; } /** * Check whether changing from LPM to blank can be done * * @return TRUE if blanking is possible, FALSE otherwise */ static gboolean mdy_blanking_can_blank_from_low_power_mode(void) { // allow if LPM is not supposed to be used anyway if( !mdy_use_low_power_mode ) return TRUE; // always allow in MALF if( submode & MCE_SUBMODE_MALF ) return TRUE; // always allow during active call if( call_state == CALL_STATE_RINGING || call_state == CALL_STATE_ACTIVE ) return TRUE; #if 0 // for reference, old logic: allow after proximity tklock set if( submode & MCE_SUBMODE_PROXIMITY_TKLOCK ) return TRUE; #else // TODO: we need proximity locking back in, for now just allow it // when tklocked if( mdy_blanking_from_lockscreen() ) return TRUE; #endif return FALSE; } // TIMER: ON -> DIM /** Get ON->DIM delay excluding adaptive dimming */ static gint mdy_blanking_get_default_dimming_delay(void) { /* Assume the normal setting is used */ gint dim_timeout = mdy_disp_dim_timeout_default; /* Use different setting if hw kbd is available */ if( keyboard_available_state == COVER_OPEN && mdy_disp_dim_timeout_keyboard > 0 ) { dim_timeout = mdy_disp_dim_timeout_keyboard; } /* After boot delay */ gint boot_delay = mdy_blanking_get_afterboot_delay(); if( dim_timeout < boot_delay ) dim_timeout = boot_delay; /* In act dead mode blanking timeouts are capped */ if( system_state == MCE_SYSTEM_STATE_ACTDEAD ) { if( dim_timeout > ACTDEAD_MAX_DIM_TIMEOUT ) dim_timeout = ACTDEAD_MAX_DIM_TIMEOUT; } return dim_timeout; } /** Get ON->DIM delay including adaptive dimming */ static gint mdy_blanking_get_dimming_delay(void) { /* Default to dimming delay setting */ gint dim_timeout = mdy_blanking_get_default_dimming_delay(); /* Check if adaptive dimming delay applies */ if( mdy_adaptive_dimming_is_enabled() ) { gint dim_adaptive = mdy_blanking_get_adaptive_dimming_delay(); if( dim_timeout < dim_adaptive ) dim_timeout = dim_adaptive; } return dim_timeout; } /** Display dimming timeout callback ID */ static guint mdy_blanking_dim_cb_id = 0; /** * Timeout callback for display dimming * * @param data Unused * @return Always returns FALSE, to disable the timeout */ static gboolean mdy_blanking_dim_cb(gpointer data) { (void)data; mce_log(LL_DEBUG, "DIM timer triggered"); display_state_t display = MCE_DISPLAY_DIM; mdy_blanking_dim_cb_id = 0; mdy_blanking_inhibit_schedule_broadcast(); /* If device is in MALF state skip dimming since systemui * isn't working yet */ if( submode & MCE_SUBMODE_MALF ) display = MCE_DISPLAY_OFF; mce_datapipe_request_display_state(display); return FALSE; } /** * Cancel display dimming timeout */ static void mdy_blanking_cancel_dim(void) { /* Remove the timeout source for display dimming */ if (mdy_blanking_dim_cb_id != 0) { mce_log(LL_DEBUG, "DIM timer canceled"); g_source_remove(mdy_blanking_dim_cb_id), mdy_blanking_dim_cb_id = 0; mdy_blanking_inhibit_schedule_broadcast(); } } /** * Setup dim timeout */ static void mdy_blanking_schedule_dim(void) { mdy_blanking_cancel_dim(); /* Do not reprogram timer if never-blank mode is active */ if( mdy_disp_never_blank ) goto EXIT; gint dim_timeout = mdy_blanking_get_dimming_delay(); mce_log(LL_DEBUG, "DIM timer scheduled @ %d secs", dim_timeout); /* Setup new timeout */ mdy_blanking_dim_cb_id = g_timeout_add_seconds(dim_timeout, mdy_blanking_dim_cb, NULL); mdy_blanking_inhibit_schedule_broadcast(); EXIT: return; } /** Idle callback id for delayed blanking inhibit signaling */ static guint mdy_blanking_inhibit_broadcast_id = 0; /** Idle callback for delayed blanking inhibit signaling * * @param aptr (unused) * * @return FALSE to stop idle callback from repeating */ static gboolean mdy_blanking_inhibit_broadcast_cb(gpointer aptr) { (void)aptr; if( !mdy_blanking_inhibit_broadcast_id ) goto EXIT; mdy_blanking_inhibit_broadcast_id = 0; mdy_dbus_send_blanking_inhibit_status(0); EXIT: return FALSE; } /** Schedule blanking inhibit signaling * * Idle callback is used to delay the sending of the D-Bus * signal so that timer reprogramming etc temporary changes * do not cause swarm of signals to be broadcast. */ static void mdy_blanking_inhibit_schedule_broadcast(void) { if( mdy_blanking_inhibit_broadcast_id ) goto EXIT; mdy_blanking_inhibit_broadcast_id = g_idle_add(mdy_blanking_inhibit_broadcast_cb, 0); EXIT: return; } /** Cancel pending delayed blanking inhibit signaling */ static void mdy_blanking_inhibit_cancel_broadcast(void) { if( !mdy_blanking_inhibit_broadcast_id ) goto EXIT; g_source_remove(mdy_blanking_inhibit_broadcast_id), mdy_blanking_inhibit_broadcast_id = 0; EXIT: return; } // TIMER: DIM -> OFF /** Display blanking timeout callback ID */ static guint mdy_blanking_off_cb_id = 0; /** * Timeout callback for display blanking * * @param data Unused * @return Always returns FALSE, to disable the timeout */ static gboolean mdy_blanking_off_cb(gpointer data) { (void)data; if( !mdy_blanking_off_cb_id ) goto EXIT; mce_log(LL_DEBUG, "BLANK timer triggered"); mdy_blanking_off_cb_id = 0; mdy_blanking_inhibit_schedule_broadcast(); /* Default to: display off */ display_state_t next_state = MCE_DISPLAY_OFF; /* Use lpm on, if starting from on/dim and tklock is already set */ switch( display_state_curr ) { case MCE_DISPLAY_ON: case MCE_DISPLAY_DIM: if( lipstick_service_state != SERVICE_STATE_RUNNING ) break; if( mdy_blanking_from_lockscreen() ) next_state = MCE_DISPLAY_LPM_ON; break; default: break; } mce_datapipe_request_display_state(next_state); /* Remove wakelock unless the timer got re-programmed */ if( !mdy_blanking_off_cb_id ) wakelock_unlock("mce_lpm_off"); EXIT: return FALSE; } /** * Cancel the display blanking timeout */ static void mdy_blanking_cancel_off(void) { /* Remove the timeout source for display blanking */ if( mdy_blanking_off_cb_id != 0 ) { mce_log(LL_DEBUG, "BLANK timer cancelled"); g_source_remove(mdy_blanking_off_cb_id); mdy_blanking_off_cb_id = 0; mdy_blanking_inhibit_schedule_broadcast(); /* unlock on cancellation */ wakelock_unlock("mce_lpm_off"); } } /** * Setup blank timeout * * This needs to use a wakelock so that the device will not * suspend when LPM_OFF -> OFF transition is scheduled. */ static void mdy_blanking_schedule_off(void) { gint timeout = mdy_blank_timeout; /* Just disable timer if never-blank mode is active */ if( mdy_disp_never_blank ) { mdy_blanking_cancel_off(); goto EXIT; } if( uiexception_type & UIEXCEPTION_TYPE_CALL ) { /* During calls: Use unadjusted default timeout */ } else if( system_state == MCE_SYSTEM_STATE_ACTDEAD ) { /* Utilize the same configurable blanking delay as * what is used for the lockscreen, but cap it to a * sensible maximum value. */ timeout = mdy_blank_from_lockscreen_timeout; if( timeout > ACTDEAD_MAX_OFF_TIMEOUT ) timeout = ACTDEAD_MAX_OFF_TIMEOUT; } else if( display_state_curr == MCE_DISPLAY_LPM_OFF ) { timeout = mdy_blank_from_lpm_off_timeout; } else if( mdy_blanking_from_lockscreen() ) { /* In case UI boots up to lockscreen, we need to * apply additional after-boot delay also to * blanking timer. */ timeout = mdy_blanking_get_afterboot_delay(); if( timeout < mdy_blank_from_lockscreen_timeout ) timeout = mdy_blank_from_lockscreen_timeout; } /* Blanking pause can optionally stay in dimmed state */ if( display_state_curr == MCE_DISPLAY_DIM && mdy_blanking_is_paused() && mdy_blanking_pause_can_dim() ) { mdy_blanking_cancel_off(); goto EXIT; } if( mdy_blanking_off_cb_id ) { g_source_remove(mdy_blanking_off_cb_id); mce_log(LL_DEBUG, "BLANK timer rescheduled @ %d secs", timeout); } else { wakelock_lock("mce_lpm_off", -1); mce_log(LL_DEBUG, "BLANK timer scheduled @ %d secs", timeout); } /* Use idle callback for zero timeout */ if( timeout > 0 ) mdy_blanking_off_cb_id = g_timeout_add(timeout * 1000, mdy_blanking_off_cb, 0); else mdy_blanking_off_cb_id = g_idle_add(mdy_blanking_off_cb, 0); mdy_blanking_inhibit_schedule_broadcast(); EXIT: return; } // TIMER: LPM_ON -> LPM_OFF /** Low power mode proximity blank timeout callback ID */ static guint mdy_blanking_lpm_off_cb_id = 0; /** * Timeout callback for low power mode proximity blank * * @param data Unused * @return Always returns FALSE, to disable the timeout */ static gboolean mdy_blanking_lpm_off_cb(gpointer data) { (void)data; mce_log(LL_DEBUG, "LPM-BLANK timer triggered"); mdy_blanking_lpm_off_cb_id = 0; mce_datapipe_request_display_state(MCE_DISPLAY_LPM_OFF); return FALSE; } /** * Cancel the low power mode proximity blank timeout */ static void mdy_blanking_cancel_lpm_off(void) { /* Remove the timeout source for low power mode */ if (mdy_blanking_lpm_off_cb_id != 0) { mce_log(LL_DEBUG, "LPM-BLANK timer cancelled"); g_source_remove(mdy_blanking_lpm_off_cb_id); mdy_blanking_lpm_off_cb_id = 0; } } /** * Setup low power mode proximity blank timeout if supported */ static void mdy_blanking_schedule_lpm_off(void) { gint timeout = mdy_blank_from_lpm_on_timeout; mdy_blanking_cancel_lpm_off(); /* Do not reprogram timer if never-blank mode is active */ if( mdy_disp_never_blank ) goto EXIT; /* Setup new timeout */ mce_log(LL_DEBUG, "LPM-BLANK timer scheduled @ %d secs", timeout); mdy_blanking_lpm_off_cb_id = g_timeout_add_seconds(timeout, mdy_blanking_lpm_off_cb, NULL); EXIT: return; } // PERIOD: BLANKING PAUSE /** Cached blanking pause is allowed -policy decision */ static tristate_t blanking_pause_allowed = TRISTATE_UNKNOWN; /** Evaluate whether clients are allowed to pause blanking * * Check whether device in such a state where clients are * allowed to pause display blanking. */ static void mdy_blanking_pause_evaluate_allowed(void) { tristate_t allowed = TRISTATE_FALSE; /* Feature must not be disabled in config */ if( !mdy_blanking_pause_is_allowed() ) goto DONE; /* Display must be currently on */ switch( display_state_curr ) { case MCE_DISPLAY_ON: // always allowed break; case MCE_DISPLAY_DIM: // optionally allowed if( mdy_blanking_pause_can_dim() ) break; // fall through default: goto DONE; } /* Only ON <--> DIM transitions are tolerated */ switch( display_state_next ) { case MCE_DISPLAY_ON: // always allowed break; case MCE_DISPLAY_DIM: // optionally allowed if( mdy_blanking_pause_can_dim() ) break; // fall through default: goto DONE; } /* Tklock must be off ... * * ... unless lockscreen expects user interaction * and there is some application on top of lockscreen */ if( submode & MCE_SUBMODE_TKLOCK ) { if( !interaction_expected ) goto DONE; if( topmost_window_pid == -1 ) goto DONE; } allowed = TRISTATE_TRUE; DONE: if( blanking_pause_allowed != allowed ) { mce_log(LL_DEBUG, "blanking_pause_allowed: %s -> %s", tristate_repr(blanking_pause_allowed), tristate_repr(allowed)); blanking_pause_allowed = allowed; if( blanking_pause_allowed != TRISTATE_TRUE ) mdy_blanking_remove_pause_clients(); mdy_dbus_send_blanking_pause_allowed_status(0); } } /** ID for display blank prevention timer source */ static guint mdy_blanking_pause_period_cb_id = 0; /** * Timeout callback for display blanking pause * * @param data Unused * * @return Always returns FALSE, to disable the timeout */ static gboolean mdy_blanking_pause_period_cb(gpointer data) { (void)data; if( mdy_blanking_pause_period_cb_id ) { mce_log(LL_DEVEL, "BLANKING PAUSE timeout"); mdy_blanking_pause_period_cb_id = 0; mdy_blanking_remove_pause_clients(); mdy_dbus_send_blanking_pause_status(0); mdy_blanking_rethink_timers(true); } return FALSE; } /** * Cancel blank prevention timeout */ static void mdy_blanking_stop_pause_period(void) { if( mdy_blanking_pause_period_cb_id ) { mce_log(LL_DEVEL, "BLANKING PAUSE cancelled"); g_source_remove(mdy_blanking_pause_period_cb_id), mdy_blanking_pause_period_cb_id = 0; mdy_dbus_send_blanking_pause_status(0); mdy_blanking_rethink_timers(true); } } /** * Prevent screen blanking for display_timeout seconds */ static void mdy_blanking_start_pause_period(int duration) { bool was_paused = (mdy_blanking_pause_period_cb_id != 0); /* Cancel existing timeout */ if( mdy_blanking_pause_period_cb_id ) g_source_remove(mdy_blanking_pause_period_cb_id); /* Add suitable amount of slack */ duration += mdy_blank_prevent_slack; /* Setup new timeout */ mdy_blanking_pause_period_cb_id = g_timeout_add(duration, mdy_blanking_pause_period_cb, 0); mce_log(LL_DEBUG, "BLANKING PAUSE started; period = %d", duration); if( !was_paused ) { mdy_dbus_send_blanking_pause_status(0); mdy_blanking_rethink_timers(true); } } /** List of monitored blanking pause clients */ static GHashTable *bpclient_lut = 0; static void mdy_blanking_init_pause_client_tracking(void) { bpclient_lut = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, bpclient_delete_cb); } static void mdy_blanking_quit_pause_client_tracking(void) { if( bpclient_lut ) g_hash_table_unref(bpclient_lut), bpclient_lut = 0; } /** Blanking pause is active predicate * * @returns true if there are active clients, false otherwise */ static bool mdy_blanking_is_paused(void) { return mdy_blanking_pause_period_cb_id != 0; //return mdy_blanking_pause_clients != 0; } /** Dimming allowed while blanking is paused predicate * * returns true if dimming is allowed, false otherwise */ static bool mdy_blanking_pause_can_dim(void) { return mdy_blanking_pause_mode == BLANKING_PAUSE_MODE_ALLOW_DIM; } /** Blanking pause is allowed predicate * * returns true if blanking pause is allowed, false otherwise */ static bool mdy_blanking_pause_is_allowed(void) { return mdy_blanking_pause_mode != BLANKING_PAUSE_MODE_DISABLED; } /** Add blanking pause client * * @param name The private the D-Bus name of the client */ void mdy_blanking_add_pause_client(const char *name) { if( !name ) goto EXIT; if( blanking_pause_allowed != TRISTATE_TRUE ) { mce_log(LL_DEBUG, "blanking pause request from`%s ignored", name); goto EXIT; } if( !bpclient_lut ) goto EXIT; bpclient_t *client = g_hash_table_lookup(bpclient_lut, name); if( !client ) { client = bpclient_create(name); g_hash_table_replace(bpclient_lut, g_strdup(name), client); } bpclient_update_timeout(client); EXIT: return; } /** Remove blanking pause client * * @param name The private the D-Bus name of the client * * @return TRUE on success, FALSE if name is NULL */ static void mdy_blanking_remove_pause_client(const char *name) { if( !bpclient_lut ) goto EXIT; if( !name ) goto EXIT; bpclient_t *client = g_hash_table_lookup(bpclient_lut, name); if( !client ) goto EXIT; g_hash_table_remove(bpclient_lut, name); mdy_blanking_evaluate_pause_timeout(); EXIT: return; } /** Remove all clients, stop blanking pause */ static void mdy_blanking_remove_pause_clients(void) { g_hash_table_remove_all(bpclient_lut); mdy_blanking_evaluate_pause_timeout(); } void mdy_blanking_evaluate_pause_timeout(void) { int dur = 0; if( !bpclient_lut ) goto EXIT; int64_t now = mce_lib_get_boot_tick(); int64_t tmo = 0; GSList *stale = 0; GHashTableIter iter; gpointer key, value; g_hash_table_iter_init(&iter, bpclient_lut); while( g_hash_table_iter_next (&iter, &key, &value) ) { const char *name = key; bpclient_t *client = value; if( client->bpc_tmo <= now ) { mce_log(LL_DEBUG, "client %s is stale", name); stale = g_slist_prepend(stale, (gpointer)name); continue; } if( client->bpc_pid == -1 ) { mce_log(LL_DEBUG, "client %s is not identified", name); continue; } if( (submode & MCE_SUBMODE_TKLOCK) && client->bpc_pid != topmost_window_pid ) { mce_log(LL_WARN, "tklocked and client %s is not topmost", name); continue; } mce_log(LL_DEBUG, "client %s is holding blanking pause", name); if( tmo < client->bpc_tmo ) tmo = client->bpc_tmo; } while( stale ) { const char *name = stale->data; g_hash_table_remove(bpclient_lut, name); stale = g_slist_remove(stale, name); } if( tmo > now ) dur = (int)(tmo - now); EXIT: mce_log(LL_DEBUG, "blanking paused for %d ms", dur); if( dur > 0 ) mdy_blanking_start_pause_period(dur); else mdy_blanking_stop_pause_period(); return; } // PERIOD: ADAPTIVE DIMMING /** Predicate for: adaptive dimming is enabled and allowed */ static bool mdy_adaptive_dimming_is_enabled(void) { bool enabled = false; if( system_state != MCE_SYSTEM_STATE_USER ) goto EXIT; if( !mdy_adaptive_dimming_enabled ) goto EXIT; if( mdy_adaptive_dimming_threshold <= 0 ) goto EXIT; enabled = true; EXIT: return enabled; } /** Get adaptive ON->DIM delay */ static gint mdy_blanking_get_adaptive_dimming_delay(void) { return mdy_disp_dim_timeout_adaptive; } /** Set adaptive ON->DIM delay */ static void mdy_blanking_set_adaptive_dimming_delay(int timeout) { if( mdy_disp_dim_timeout_adaptive != timeout ) { mce_log(LL_DEBUG, "mdy_disp_dim_timeout_adaptive: %d -> %d", mdy_disp_dim_timeout_adaptive, timeout); mdy_disp_dim_timeout_adaptive = timeout; } } /** Reset adaptive ON->DIM delay */ static void mdy_blanking_reset_adaptive_dimming_delay(void) { mdy_blanking_set_adaptive_dimming_delay(0); } /** Timer ID for unpriming adaptive dimming */ static guint mdy_blanking_adaptive_dimming_unprime_id = 0; /** Timeout callback for adaptive dimming timeout * * @param data Unused * * @return Always returns FALSE, to disable the timeout */ static gboolean mdy_blanking_adaptive_dimming_unprime_cb(gpointer data) { (void)data; mdy_blanking_adaptive_dimming_unprime_id = 0; mdy_blanking_reset_adaptive_dimming_delay(); return FALSE; } /** Cancel the adaptive dimming timeout */ static void mdy_blanking_unprime_adaptive_dimming(void) { /* Remove the timeout source for adaptive dimming */ if( mdy_blanking_adaptive_dimming_unprime_id ) { g_source_remove(mdy_blanking_adaptive_dimming_unprime_id); mdy_blanking_adaptive_dimming_unprime_id = 0; } } /** When applicable, bump dimming timeout length */ static void mdy_blanking_trigger_adaptive_dimming(void) { /* Skip if not primed */ if( !mdy_blanking_adaptive_dimming_unprime_id ) goto EXIT; /* Start with current dim delay */ gint current = mdy_blanking_get_dimming_delay(); /* Find next larger step from list of dim delays */ for( GSList *item = mdy_possible_dim_timeouts; item; item = item->next ) { gint timeout = GPOINTER_TO_INT(item->data); if( timeout < mdy_disp_dim_timeout_adaptive ) continue; if( timeout <= current ) continue; current = timeout; break; } /* Update value to be used */ mdy_blanking_set_adaptive_dimming_delay(current); EXIT: return; } /** Setup adaptive dimming timeout */ static void mdy_blanking_prime_adaptive_dimming(void) { mdy_blanking_unprime_adaptive_dimming(); if( !mdy_adaptive_dimming_is_enabled() ) goto EXIT; if( display_state_next != MCE_DISPLAY_DIM ) goto EXIT; /* Setup new timeout */ mdy_blanking_adaptive_dimming_unprime_id = g_timeout_add(mdy_adaptive_dimming_threshold, mdy_blanking_adaptive_dimming_unprime_cb, NULL); EXIT: return; } // AUTOMATIC BLANKING STATE MACHINE /** Check if blanking inhibit mode denies turning display off * * @return true if automatic blanking should not happen, false otherwise */ static bool mdy_blanking_inhibit_off_p(void) { bool inhibit = false; /* Blanking inhibit is explicitly ignored in act dead */ switch( system_state ) { case MCE_SYSTEM_STATE_ACTDEAD: goto EXIT; default: break; } /* Evaluate charger related blanking inhibit policy */ switch( mdy_blanking_inhibit_mode ) { case INHIBIT_STAY_DIM: inhibit = true; break; case INHIBIT_STAY_DIM_WITH_CHARGER: if( charger_state == CHARGER_STATE_ON ) inhibit = true; break; default: break; } /* Evaluate kbd slide related blanking inhibit policy */ switch( mdy_kbd_slide_inhibit_mode ) { case KBD_SLIDE_INHIBIT_STAY_DIM_WHEN_OPEN: if( keyboard_slide_input_state == COVER_OPEN ) inhibit = true; break; default: break; } EXIT: return inhibit; } /** Check if blanking inhibit mode denies dimming display * * @return true if automatic dimming should not happen, false otherwise */ static bool mdy_blanking_inhibit_dim_p(void) { bool inhibit = false; /* Blanking inhibit is explicitly ignored in act dead */ switch( system_state ) { case MCE_SYSTEM_STATE_ACTDEAD: goto EXIT; default: break; } /* Evaluate charger related blanking inhibit policy */ switch( mdy_blanking_inhibit_mode ) { case INHIBIT_STAY_ON: inhibit = true; break; case INHIBIT_STAY_ON_WITH_CHARGER: if( charger_state == CHARGER_STATE_ON ) inhibit = true; break; default: break; } /* Evaluate kbd slide related blanking inhibit policy */ switch( mdy_kbd_slide_inhibit_mode ) { case KBD_SLIDE_INHIBIT_STAY_ON_WHEN_OPEN: if( keyboard_slide_input_state == COVER_OPEN ) inhibit = true; break; default: break; } EXIT: return inhibit; } /** Reprogram blanking timers */ static void mdy_blanking_rethink_timers(bool force) { // TRIGGERS: // submode <- mdy_datapipe_submode_cb() // display_state_curr<- mdy_display_state_changed() // audio_route <- mdy_datapipe_audio_route_cb() // charger_state <- mdy_datapipe_charger_state_cb() // uiexception_type <- mdy_datapipe_uiexception_type_cb() // call_state <- mdy_datapipe_call_state_trigger_cb() // // INPUTS: // proximity_sensor_actual <- mdy_datapipe_proximity_sensor_actual_cb() // mdy_blanking_inhibit_mode <- mdy_setting_cb() // mdy_blanking_is_paused() static display_state_t prev_display_state = MCE_DISPLAY_UNDEF; static cover_state_t prev_proximity_state = COVER_UNDEF; static uiexception_type_t prev_uiexception_type = UIEXCEPTION_TYPE_NONE; static call_state_t prev_call_state = CALL_STATE_NONE; static charger_state_t prev_charger_state = CHARGER_STATE_UNDEF; static audio_route_t prev_audio_route = AUDIO_ROUTE_HANDSET; static bool prev_tklock_mode = false; bool tklock_mode = mdy_blanking_from_lockscreen(); if( prev_tklock_mode != tklock_mode ) force = true; if( prev_audio_route != audio_route ) force = true; if( prev_charger_state != charger_state ) force = true; if( prev_uiexception_type != uiexception_type ) force = true; if( prev_call_state != call_state ) force = true; if( prev_proximity_state != proximity_sensor_actual ) force = true; if( prev_display_state != display_state_curr ) { force = true; /* Stop blanking pause period, unless toggling between * ON and DIM states while dimming during blanking * pause is allowed */ if( (prev_display_state == MCE_DISPLAY_ON || prev_display_state == MCE_DISPLAY_DIM) && (display_state_curr == MCE_DISPLAY_ON || display_state_curr == MCE_DISPLAY_DIM) && mdy_blanking_is_paused() && mdy_blanking_pause_can_dim() ) { // keep existing blanking pause timer alive } else { // stop blanking pause period mdy_blanking_stop_pause_period(); } // handle adaptive blanking states switch( display_state_curr ) { default: case MCE_DISPLAY_UNDEF: case MCE_DISPLAY_OFF: case MCE_DISPLAY_LPM_OFF: case MCE_DISPLAY_LPM_ON: case MCE_DISPLAY_POWER_UP: case MCE_DISPLAY_POWER_DOWN: mdy_blanking_unprime_adaptive_dimming(); mdy_blanking_reset_adaptive_dimming_delay(); break; case MCE_DISPLAY_DIM: mdy_blanking_prime_adaptive_dimming(); break; case MCE_DISPLAY_ON: mdy_blanking_unprime_adaptive_dimming(); break; } } mce_log(LL_DEBUG, "update %s", force ? "YES" : "NO"); if( !force ) goto EXIT; mdy_blanking_cancel_dim(); mdy_blanking_cancel_off(); mdy_blanking_cancel_lpm_off(); /* Skip timer programming in never-blank mode */ if( mdy_disp_never_blank ) goto EXIT; if( uiexception_type & ~UIEXCEPTION_TYPE_CALL ) { /* exceptional ui states other than * call ui -> no dim/blank timers */ goto EXIT; } switch( display_state_curr ) { case MCE_DISPLAY_OFF: break; case MCE_DISPLAY_LPM_OFF: mdy_blanking_schedule_off(); break; case MCE_DISPLAY_LPM_ON: mdy_blanking_schedule_lpm_off(); break; case MCE_DISPLAY_DIM: if( mdy_osupdate_running ) break; if( mdy_blanking_inhibit_off_p() ) break; if( tklock_mode && mdy_blanking_from_tklock_disabled ) break; mdy_blanking_schedule_off(); break; case MCE_DISPLAY_ON: /* In update mode auto-blanking must not occur. */ if( mdy_osupdate_running ) break; /* All exceptional states apart from active call * should block auto-blanking. */ if( uiexception_type ) { /* Not call related -> block auto-blanking */ if( uiexception_type & ~UIEXCEPTION_TYPE_CALL ) break; /* Incoming call -> block auto-blanking */ if( call_state == CALL_STATE_RINGING ) break; /* Even during during active calls we do not want * to interfere with proximity blanking */ if( audio_route == AUDIO_ROUTE_HANDSET && proximity_sensor_actual == COVER_CLOSED ) break; } /* In act-dead mode & co blanking inhibit modes are * ignored and display is blanked without going through * the dimming step */ if( system_state != MCE_SYSTEM_STATE_USER ) { mdy_blanking_schedule_off(); break; } /* When lockscreen is active, inhibit modes are ignored * and display is blanked without going through the * dimming step. * * However, to deal with incoming calls received while * lockscreen and/or device lock is active, normal * dimming rules must be applied during active calls. */ if( tklock_mode && !(uiexception_type & UIEXCEPTION_TYPE_CALL) ) { if( !mdy_blanking_from_tklock_disabled ) mdy_blanking_schedule_off(); break; } /* Check if blanking pause is blocking dimming */ if( mdy_blanking_is_paused() && !mdy_blanking_pause_can_dim() ) break; /* Check if inhibit mode is blocking dimming */ if( mdy_blanking_inhibit_dim_p() ) break; /* Schedule auto-dim */ mdy_blanking_schedule_dim(); break; default: case MCE_DISPLAY_UNDEF: case MCE_DISPLAY_POWER_UP: case MCE_DISPLAY_POWER_DOWN: break; } EXIT: prev_display_state = display_state_curr; prev_proximity_state = proximity_sensor_actual; prev_uiexception_type = uiexception_type; prev_call_state = call_state; prev_charger_state = charger_state; prev_audio_route = audio_route; prev_tklock_mode = tklock_mode; return; } /** Reprogram blanking timers on proximity triggers */ static void mdy_blanking_rethink_proximity(void) { switch( display_state_curr ) { case MCE_DISPLAY_LPM_ON: if( proximity_sensor_actual == COVER_CLOSED ) mce_datapipe_request_display_state(MCE_DISPLAY_LPM_OFF); else mdy_blanking_schedule_lpm_off(); break; case MCE_DISPLAY_LPM_OFF: if( proximity_sensor_actual == COVER_OPEN && lid_sensor_filtered != COVER_CLOSED ) mce_datapipe_request_display_state(MCE_DISPLAY_LPM_ON); else mdy_blanking_schedule_off(); break; default: case MCE_DISPLAY_ON: case MCE_DISPLAY_DIM: case MCE_DISPLAY_UNDEF: case MCE_DISPLAY_OFF: case MCE_DISPLAY_POWER_UP: case MCE_DISPLAY_POWER_DOWN: break; } } /** Cancel all timers that are display state specific */ static void mdy_blanking_cancel_timers(void) { mdy_blanking_cancel_dim(); mdy_blanking_cancel_off(); mdy_blanking_cancel_lpm_off(); mdy_brightness_stop_fade_timer(); } /** After bootup autoblank prevent; initially neither enabled nor active */ static bool mdy_blanking_afterboot_delay_enabled = false; static bool mdy_blanking_afterboot_delay_started = false; static int64_t mdy_blanking_afterboot_delay_ends = 0; /** Get delay until end of after boot blank prevent * * @return seconds to end of after boot blank prevent, or * zero if time limit has been already passed */ static gint mdy_blanking_get_afterboot_delay(void) { gint delay = 0; if( mdy_blanking_afterboot_delay_ends ) { int64_t now = mce_lib_get_boot_tick(); int64_t tmo = mdy_blanking_afterboot_delay_ends - now; if( tmo > 0 ) { /* milliseconds to seconds, round up */ delay = (gint)((tmo + 999) / 1000); } } return delay; } /** Evaluate whether device startup has reached "after boot" stage * * @return true if conditions are met, false otherwise */ static bool mdy_blanking_afterboot_delay_start_p(void) { bool start = false; /* Bootup has not yet finished */ if( mdy_init_done == TRISTATE_TRUE ) goto DONE; /* We are booting to USER mode */ if( mdy_bootstate != BOOTSTATE_USER ) goto DONE; if( system_state != MCE_SYSTEM_STATE_USER ) goto DONE; /* Lipstick has started */ if( lipstick_service_state != SERVICE_STATE_RUNNING ) goto DONE; /* Display is/soon will be powered on */ if( display_state_next != MCE_DISPLAY_ON ) goto DONE; start = true; DONE: return start; } /** Evaluate need for longer after-boot blanking delay */ static void mdy_blanking_rethink_afterboot_delay(void) { /* The feature should be enabled only when mce process * gets started as part of regular bootup sequence, which * in practice means: Getting to observe that init-done * has not been reached yet. */ if( !mdy_blanking_afterboot_delay_enabled ) { if( mdy_init_done != TRISTATE_FALSE ) goto EXIT; mdy_blanking_afterboot_delay_enabled = true; mce_log(LL_DEBUG, "after boot blank prevent enabled"); } /* Activate once after bootup has progressed far enough. * And deactivate after reaching the after-boot timeout. */ int64_t prev = mdy_blanking_afterboot_delay_ends; if( !mdy_blanking_afterboot_delay_started ) { /* Enabled, but not yet activated */ if( mdy_blanking_afterboot_delay_start_p() ) { /* Activate */ mdy_blanking_afterboot_delay_started = true; mdy_blanking_afterboot_delay_ends = mce_lib_get_boot_tick() + AFTERBOOT_BLANKING_TIMEOUT * 1000; } } else if( mdy_blanking_afterboot_delay_ends ) { /* Enabled and active */ if( mdy_blanking_afterboot_delay_ends <= mce_lib_get_boot_tick() ) { /* Deactivate */ mdy_blanking_afterboot_delay_ends = 0; } } if( prev == mdy_blanking_afterboot_delay_ends ) goto EXIT; mce_log(LL_DEBUG, "after boot blank prevent %s", mdy_blanking_afterboot_delay_ends ? "activated" : "deactivated"); /* If dim/blank timer is running, reprogram it */ if( mdy_blanking_dim_cb_id ) mdy_blanking_schedule_dim(); else if( mdy_blanking_off_cb_id ) mdy_blanking_schedule_off(); EXIT: return; } /* ========================================================================= * * DISPLAY_TYPE_PROBING * ========================================================================= */ /** Callback function for logging errors within glob() * * @param path path to file/dir where error occurred * @param err errno that occurred * * @return 0 (= do not stop glob) */ static int mdy_display_type_glob_err_cb(const char *path, int err) { mce_log(LL_WARN, "%s: glob: %s", path, g_strerror(err)); return 0; } /** Check if sysfs directory contains brightness and max_brightness entries * * @param sysfs directory to probe * @param setpath place to store path to brightness file * @param maxpath place to store path to max_brightness file * @return TRUE if brightness and max_brightness files were found, * FALSE otherwise */ static gboolean mdy_display_type_probe_brightness(const gchar *dirpath, char **setpath, char **maxpath) { gboolean res = FALSE; gchar *set = g_strdup_printf("%s/brightness", dirpath); gchar *max = g_strdup_printf("%s/max_brightness", dirpath); if( set && max && !g_access(set, W_OK) && !g_access(max, R_OK) ) { *setpath = set, set = 0; *maxpath = max, max = 0; res = TRUE; } g_free(set); g_free(max); return res; } /** Get the display type from MCE_CONF_DISPLAY_GROUP config group * * @param display_type where to store the selected display type * * @return TRUE if valid configuration was found, FALSE otherwise */ static gboolean mdy_display_type_get_from_config(display_type_t *display_type) { gboolean res = FALSE; gchar *set = 0; gchar *max = 0; gchar **vdir = 0; gchar **vset = 0; gchar **vmax = 0; gsize nset = 0; gsize nmax = 0; /* First check if we have a configured brightness directory * that a) exists and b) contains both brightness and * max_brightness files */ vdir = mce_conf_get_string_list(MCE_CONF_DISPLAY_GROUP, MCE_CONF_BACKLIGHT_DIRECTORY, 0); if( vdir ) { for( size_t i = 0; vdir[i]; ++i ) { if( !*vdir[i] || g_access(vdir[i], F_OK) ) continue; if( mdy_display_type_probe_brightness(vdir[i], &set, &max) ) goto EXIT; } } /* Then check if we can find mathes from possible brightness and * max_brightness file lists */ vset = mce_conf_get_string_list(MCE_CONF_DISPLAY_GROUP, MCE_CONF_BACKLIGHT_PATH, &nset); vmax = mce_conf_get_string_list(MCE_CONF_DISPLAY_GROUP, MCE_CONF_MAX_BACKLIGHT_PATH, &nmax); if( nset != nmax ) { mce_log(LL_WARN, "%s and %s do not have the same amount of " "configuration entries", MCE_CONF_BACKLIGHT_PATH, MCE_CONF_MAX_BACKLIGHT_PATH); } if( nset > nmax ) nset = nmax; for( gsize i = 0; i < nset; ++i ) { if( *vset[i] == 0 || g_access(vset[i], W_OK) != 0 ) continue; if( *vmax[i] == 0 || g_access(vmax[i], R_OK) != 0 ) continue; set = g_strdup(vset[i]); max = g_strdup(vmax[i]); break; } EXIT: /* Have we found both brightness and max_brightness files? */ if( set && max ) { mce_log(LL_NOTICE, "applying DISPLAY_TYPE_GENERIC from config file"); mce_log(LL_NOTICE, "brightness path = %s", set); mce_log(LL_NOTICE, "max_brightness path = %s", max); mdy_brightness_level_output.path = set, set = 0; mdy_brightness_level_maximum_path = max, max = 0; mdy_cabc_mode_file = 0; mdy_cabc_available_modes_file = 0; mdy_cabc_is_supported = 0; *display_type = DISPLAY_TYPE_GENERIC; res = TRUE; } g_free(max); g_free(set); g_strfreev(vmax); g_strfreev(vset); g_strfreev(vdir); return res; } /** Get the display type by looking up from sysfs * * @param display_type where to store the selected display type * * @return TRUE if valid configuration was found, FALSE otherwise */ static gboolean mdy_display_type_get_from_sysfs_probe(display_type_t *display_type) { static const char pattern[] = "/sys/class/backlight/*"; static const char * const lut[] = { /* this seems to be some kind of "Android standard" path */ "/sys/class/leds/lcd-backlight", NULL }; gboolean res = FALSE; gchar *set = 0; gchar *max = 0; glob_t gb; memset(&gb, 0, sizeof gb); /* Assume: Any match from fixed list will be true positive. * Check them before possibly ambiguous backlight class entries. */ for( size_t i = 0; lut[i]; ++i ) { if( mdy_display_type_probe_brightness(lut[i], &set, &max) ) goto EXIT; } if( glob(pattern, 0, mdy_display_type_glob_err_cb, &gb) != 0 ) { mce_log(LL_WARN, "no backlight devices found"); goto EXIT; } if( gb.gl_pathc > 1 ) mce_log(LL_WARN, "several backlight devices present, " "choosing the first usable one"); for( size_t i = 0; i < gb.gl_pathc; ++i ) { const char *path = gb.gl_pathv[i]; if( mdy_display_type_probe_brightness(path, &set, &max) ) goto EXIT; } EXIT: /* Have we found both brightness and max_brightness files? */ if( set && max ) { mce_log(LL_NOTICE, "applying DISPLAY_TYPE_GENERIC from sysfs probe"); mce_log(LL_NOTICE, "brightness path = %s", set); mce_log(LL_NOTICE, "max_brightness path = %s", max); mdy_brightness_level_output.path = set, set = 0; mdy_brightness_level_maximum_path = max, max = 0; mdy_cabc_mode_file = 0; mdy_cabc_available_modes_file = 0; mdy_cabc_is_supported = 0; *display_type = DISPLAY_TYPE_GENERIC; res = TRUE; } g_free(max); g_free(set); globfree(&gb); return res; } static gboolean mdy_display_type_get_from_hybris(display_type_t *display_type) { #ifdef ENABLE_HYBRIS gboolean res = FALSE; if( !mce_hybris_backlight_init() ) { mce_log(LL_DEBUG, "libhybris brightness controls not available"); goto EXIT; } mce_log(LL_NOTICE, "using libhybris for display brightness control"); mdy_brightness_set_level_hook = mdy_brightness_set_level_hybris; mdy_brightness_level_maximum = 255; *display_type = DISPLAY_TYPE_GENERIC; res = TRUE; EXIT: return res; #else (void)display_type; return FALSE; #endif } /** * Get the display type * * @return The display type */ static display_type_t mdy_display_type_get(void) { static display_type_t display_type = DISPLAY_TYPE_UNSET; /* If we have the display type already, return it */ if (display_type != DISPLAY_TYPE_UNSET) goto EXIT; if( mdy_display_type_get_from_config(&display_type) ) { // nop } else if (g_access(DISPLAY_BACKLIGHT_PATH DISPLAY_ACX565AKM, W_OK) == 0) { display_type = DISPLAY_TYPE_ACX565AKM; mdy_brightness_level_output.path = g_strconcat(DISPLAY_BACKLIGHT_PATH, DISPLAY_ACX565AKM, DISPLAY_CABC_BRIGHTNESS_FILE, NULL); mdy_brightness_level_maximum_path = g_strconcat(DISPLAY_BACKLIGHT_PATH, DISPLAY_ACX565AKM, DISPLAY_CABC_MAX_BRIGHTNESS_FILE, NULL); mdy_cabc_mode_file = g_strconcat(DISPLAY_BACKLIGHT_PATH, DISPLAY_ACX565AKM, DISPLAY_CABC_MODE_FILE, NULL); mdy_cabc_available_modes_file = g_strconcat(DISPLAY_BACKLIGHT_PATH, DISPLAY_ACX565AKM, DISPLAY_CABC_AVAILABLE_MODES_FILE, NULL); mdy_cabc_is_supported = (g_access(mdy_cabc_mode_file, W_OK) == 0); } else if (g_access(DISPLAY_BACKLIGHT_PATH DISPLAY_L4F00311, W_OK) == 0) { display_type = DISPLAY_TYPE_L4F00311; mdy_brightness_level_output.path = g_strconcat(DISPLAY_BACKLIGHT_PATH, DISPLAY_L4F00311, DISPLAY_CABC_BRIGHTNESS_FILE, NULL); mdy_brightness_level_maximum_path = g_strconcat(DISPLAY_BACKLIGHT_PATH, DISPLAY_L4F00311, DISPLAY_CABC_MAX_BRIGHTNESS_FILE, NULL); mdy_cabc_mode_file = g_strconcat(DISPLAY_BACKLIGHT_PATH, DISPLAY_L4F00311, DISPLAY_CABC_MODE_FILE, NULL); mdy_cabc_available_modes_file = g_strconcat(DISPLAY_BACKLIGHT_PATH, DISPLAY_L4F00311, DISPLAY_CABC_AVAILABLE_MODES_FILE, NULL); mdy_cabc_is_supported = (g_access(mdy_cabc_mode_file, W_OK) == 0); } else if (g_access(DISPLAY_BACKLIGHT_PATH DISPLAY_TAAL, W_OK) == 0) { display_type = DISPLAY_TYPE_TAAL; mdy_brightness_level_output.path = g_strconcat(DISPLAY_BACKLIGHT_PATH, DISPLAY_TAAL, DISPLAY_CABC_BRIGHTNESS_FILE, NULL); mdy_brightness_level_maximum_path = g_strconcat(DISPLAY_BACKLIGHT_PATH, DISPLAY_TAAL, DISPLAY_CABC_MAX_BRIGHTNESS_FILE, NULL); mdy_cabc_mode_file = g_strconcat(DISPLAY_BACKLIGHT_PATH, DISPLAY_TAAL, "/device", DISPLAY_CABC_MODE_FILE, NULL); mdy_cabc_available_modes_file = g_strconcat(DISPLAY_BACKLIGHT_PATH, DISPLAY_TAAL, "/device", DISPLAY_CABC_AVAILABLE_MODES_FILE, NULL); mdy_cabc_is_supported = (g_access(mdy_cabc_mode_file, W_OK) == 0); } else if (g_access(DISPLAY_BACKLIGHT_PATH DISPLAY_HIMALAYA, W_OK) == 0) { display_type = DISPLAY_TYPE_HIMALAYA; mdy_brightness_level_output.path = g_strconcat(DISPLAY_BACKLIGHT_PATH, DISPLAY_HIMALAYA, DISPLAY_CABC_BRIGHTNESS_FILE, NULL); mdy_brightness_level_maximum_path = g_strconcat(DISPLAY_BACKLIGHT_PATH, DISPLAY_HIMALAYA, DISPLAY_CABC_MAX_BRIGHTNESS_FILE, NULL); mdy_cabc_mode_file = g_strconcat(DISPLAY_BACKLIGHT_PATH, DISPLAY_HIMALAYA, "/device", DISPLAY_CABC_MODE_FILE, NULL); mdy_cabc_available_modes_file = g_strconcat(DISPLAY_BACKLIGHT_PATH, DISPLAY_HIMALAYA, "/device", DISPLAY_CABC_AVAILABLE_MODES_FILE, NULL); mdy_cabc_is_supported = (g_access(mdy_cabc_mode_file, W_OK) == 0); } else if (g_access(DISPLAY_BACKLIGHT_PATH DISPLAY_DISPLAY0, W_OK) == 0) { display_type = DISPLAY_TYPE_DISPLAY0; mdy_brightness_level_output.path = g_strconcat(DISPLAY_BACKLIGHT_PATH, DISPLAY_DISPLAY0, DISPLAY_CABC_BRIGHTNESS_FILE, NULL); mdy_brightness_level_maximum_path = g_strconcat(DISPLAY_BACKLIGHT_PATH, DISPLAY_DISPLAY0, DISPLAY_CABC_MAX_BRIGHTNESS_FILE, NULL); mdy_cabc_mode_file = g_strconcat(DISPLAY_BACKLIGHT_PATH, DISPLAY_DISPLAY0, "/device", DISPLAY_CABC_MODE_FILE, NULL); mdy_cabc_available_modes_file = g_strconcat(DISPLAY_BACKLIGHT_PATH, DISPLAY_DISPLAY0, "/device", DISPLAY_CABC_AVAILABLE_MODES_FILE, NULL); mdy_brightness_hw_fading_output.path = g_strconcat(DISPLAY_BACKLIGHT_PATH, DISPLAY_DISPLAY0, DISPLAY_DEVICE_PATH, DISPLAY_HW_DIMMING_FILE, NULL); mdy_high_brightness_mode_output.path = g_strconcat(DISPLAY_BACKLIGHT_PATH, DISPLAY_DISPLAY0, DISPLAY_DEVICE_PATH, DISPLAY_HBM_FILE, NULL); mdy_low_power_mode_file = g_strconcat(DISPLAY_BACKLIGHT_PATH, DISPLAY_DISPLAY0, DISPLAY_DEVICE_PATH, DISPLAY_LPM_FILE, NULL); mdy_cabc_is_supported = (g_access(mdy_cabc_mode_file, W_OK) == 0); mdy_brightness_hw_fading_is_supported = (g_access(mdy_brightness_hw_fading_output.path, W_OK) == 0); mdy_high_brightness_mode_supported = (g_access(mdy_high_brightness_mode_output.path, W_OK) == 0); mdy_low_power_mode_supported = (g_access(mdy_low_power_mode_file, W_OK) == 0); /* Enable hardware fading if supported */ if (mdy_brightness_hw_fading_is_supported == TRUE) mce_write_number_string_to_file(&mdy_brightness_hw_fading_output, 1); } else if (g_access(DISPLAY_BACKLIGHT_PATH DISPLAY_ACPI_VIDEO0, W_OK) == 0) { display_type = DISPLAY_TYPE_ACPI_VIDEO0; mdy_brightness_level_output.path = g_strconcat(DISPLAY_BACKLIGHT_PATH, DISPLAY_ACPI_VIDEO0, DISPLAY_CABC_BRIGHTNESS_FILE, NULL); mdy_brightness_level_maximum_path = g_strconcat(DISPLAY_BACKLIGHT_PATH, DISPLAY_ACPI_VIDEO0, DISPLAY_CABC_MAX_BRIGHTNESS_FILE, NULL); } else if (g_access(DISPLAY_GENERIC_PATH, W_OK) == 0) { display_type = DISPLAY_TYPE_GENERIC; mdy_brightness_level_output.path = g_strconcat(DISPLAY_GENERIC_PATH, DISPLAY_GENERIC_BRIGHTNESS_FILE, NULL); mdy_brightness_level_maximum_path = g_strconcat(DISPLAY_GENERIC_PATH, DISPLAY_GENERIC_MAX_BRIGHTNESS_FILE, NULL); } else if( mdy_display_type_get_from_sysfs_probe(&display_type) ) { // nop } else if( mdy_display_type_get_from_hybris(&display_type) ) { /* nop */ } else { display_type = DISPLAY_TYPE_NONE; } errno = 0; mce_log(LL_DEBUG, "Display type: %d", display_type); EXIT: return display_type; } /* ========================================================================= * * FBDEV_SLEEP_AND_WAKEUP * ========================================================================= */ /** Input watch callback for frame buffer resume waiting * * Gets triggered when worker thread writes to pipe * * @param chn (not used) * @param cnd (not used) * @param aptr state data (as void pointer) * * @return FALSE (to disable the input watch) */ #ifdef ENABLE_WAKELOCKS static gboolean mdy_waitfb_event_cb(GIOChannel *chn, GIOCondition cnd, gpointer aptr) { (void)chn; waitfb_t *self = aptr; gboolean keep = FALSE; if( !self->pipe_id ) goto EXIT; if( cnd & (G_IO_ERR | G_IO_HUP | G_IO_NVAL) ) { goto EXIT; } char tmp[64]; int fd = g_io_channel_unix_get_fd(chn); int rc = read(fd, tmp, sizeof tmp); if( rc == -1 ) { if( errno == EINTR || errno == EAGAIN ) keep = TRUE; else mce_log(LL_ERR, "read events: %m"); goto EXIT; } if( rc == 0 ) { mce_log(LL_ERR, "read events: EOF"); goto EXIT; } keep = TRUE; self->suspended = (tmp[rc-1] == 'S'); mce_log(LL_NOTICE, "read:%d, suspended:%d", rc, self->suspended); mdy_stm_schedule_rethink(); EXIT: if( !keep && self->pipe_id ) { self->pipe_id = 0; mce_log(LL_CRIT, "stopping io watch"); mdy_waitfb_thread_stop(self); } return keep; } #endif /* ENABLE_WAKELOCKS */ /** Wait for fb sleep/wakeup thread * * Alternates between waiting for fb wakeup and sleep. * Signals mainloop about the changes via a pipe. * * @param aptr state data (as void pointer) * * @return 0 */ #ifdef ENABLE_WAKELOCKS static void *mdy_waitfb_thread_entry(void *aptr) { waitfb_t *self = aptr; /* allow quick and dirty cancellation */ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, 0); pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0); /* block in sysfs read */ char tmp[32]; int rc; for( ;; ) { // FIXME: check if mce_log() is thread safe //fprintf(stderr, "READ <<<< %s\n", self->wake_path); /* wait for fb wakeup */ self->wake_fd = TEMP_FAILURE_RETRY(open(self->wake_path, O_RDONLY)); if( self->wake_fd == -1 ) { fprintf(stderr, "%s: open: %m", self->wake_path); break; } rc = TEMP_FAILURE_RETRY(read(self->wake_fd, tmp, sizeof tmp)); if( rc == -1 ) { fprintf(stderr, "%s: %m", self->wake_path); break; } TEMP_FAILURE_RETRY(close(self->wake_fd)), self->wake_fd = -1; /* send "woke up" to mainloop */ TEMP_FAILURE_RETRY(write(self->pipe_fd, "W", 1)); //fprintf(stderr, "READ <<<< %s\n", self->sleep_path); /* wait for fb sleep */ self->sleep_fd = TEMP_FAILURE_RETRY(open(self->sleep_path, O_RDONLY)); if( self->sleep_fd == -1 ) { fprintf(stderr, "%s: open: %m", self->sleep_path); break; } rc = TEMP_FAILURE_RETRY(read(self->sleep_fd, tmp, sizeof tmp)); if( rc == -1 ) { fprintf(stderr, "%s: %m", self->sleep_path); break; } TEMP_FAILURE_RETRY(close(self->sleep_fd)), self->sleep_fd = -1; /* send "sleeping" to mainloop */ TEMP_FAILURE_RETRY(write(self->pipe_fd, "S", 1)); } /* mark thread done and exit */ self->finished = true; return 0; } #endif /* ENABLE_WAKELOCKS */ /** Start delayed display state change broadcast * * @param self state data * * @return TRUE if waiting was initiated succesfully, FALSE otherwise */ #ifdef ENABLE_WAKELOCKS static gboolean mdy_waitfb_thread_start(waitfb_t *self) { gboolean res = FALSE; GIOChannel *chn = 0; int pfd[2] = {-1, -1}; mdy_waitfb_thread_stop(self); if( lwl_probe() == SUSPEND_TYPE_AUTO ) goto EXIT; if( access(self->wake_path, F_OK) == -1 || access(self->sleep_path, F_OK) == -1 ) goto EXIT; if( pipe2(pfd, O_CLOEXEC) == -1 ) { mce_log(LL_ERR, "pipe: %m"); goto EXIT; } self->pipe_fd = pfd[1], pfd[1] = -1; if( !(chn = g_io_channel_unix_new(pfd[0])) ) { goto EXIT; } self->pipe_id = g_io_add_watch(chn, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, mdy_waitfb_event_cb, self); if( !self->pipe_id ) { goto EXIT; } g_io_channel_set_close_on_unref(chn, TRUE), pfd[0] = -1; self->finished = false; if( pthread_create(&self->thread, 0, mdy_waitfb_thread_entry, self) ) { mce_log(LL_ERR, "failed to create waitfb thread"); goto EXIT; } res = TRUE; EXIT: if( chn != 0 ) g_io_channel_unref(chn); if( pfd[1] != -1 ) close(pfd[1]); if( pfd[0] != -1 ) close(pfd[0]); /* all or nothing */ if( !res ) mdy_waitfb_thread_stop(self); return res; } #endif /* ENABLE_WAKELOCKS */ /** Release all dynamic resources related to fb resume waiting * * @param self state data */ #ifdef ENABLE_WAKELOCKS static void mdy_waitfb_thread_stop(waitfb_t *self) { /* cancel worker thread */ if( self->thread && !self->finished ) { mce_log(LL_DEBUG, "stopping waitfb thread"); if( pthread_cancel(self->thread) != 0 ) { mce_log(LL_ERR, "failed to stop waitfb thread"); } else { void *status = 0; pthread_join(self->thread, &status); mce_log(LL_DEBUG, "thread stopped, status = %p", status); } } self->thread = 0; /* remove pipe input io watch */ if( self->pipe_id ) { mce_log(LL_DEBUG, "remove pipe input watch"); g_source_remove(self->pipe_id), self->pipe_id = 0; } /* close pipe output fd */ if( self->pipe_fd != -1) { mce_log(LL_DEBUG, "close pipe write fd"); close(self->pipe_fd), self->pipe_fd = -1; } /* close sysfs input fds */ if( self->sleep_fd != -1 ) { mce_log(LL_DEBUG, "close %s", self->sleep_path); close(self->sleep_fd), self->sleep_fd = -1; } if( self->wake_fd != -1 ) { mce_log(LL_DEBUG, "close %s", self->wake_path); close(self->wake_fd), self->wake_fd = -1; } } #endif /* ENABLE_WAKELOCKS */ /** State information for wait for fb resume thread */ static waitfb_t mdy_waitfb_data = { .suspended = false, .thread = 0, .finished = false, #if 0 // debug kernel delay handling via fifos .wake_path = "/tmp/wait_for_fb_wake", .sleep_path = "/tmp/wait_for_fb_sleep", #else // real sysfs paths .wake_path = "/sys/power/wait_for_fb_wake", .sleep_path = "/sys/power/wait_for_fb_sleep", #endif .wake_fd = -1, .sleep_fd = -1, .pipe_fd = -1, .pipe_id = 0, }; /* ========================================================================= * * TOPMOST_WINDOW_TRACKING * ========================================================================= */ static void mdy_topmost_window_set_pid(int pid) { /* If there is no active application window, lipstick can report * pid as zero or -1 depending on details that are currently not * relevant from mce point of view -> normalize and use -1 value. */ if( pid <= 0 ) pid = -1; if( topmost_window_pid == pid ) goto EXIT; mce_log(LL_DEBUG, "topmost_window_pid: %d -> %d", topmost_window_pid, pid); topmost_window_pid = pid; mdy_blanking_pause_evaluate_allowed(); datapipe_exec_full(&topmost_window_pid_pipe, GINT_TO_POINTER(topmost_window_pid)); EXIT: return; } /** Handle topmost window pid changed signal * * @param sig D-Bus signal message * * @return TRUE */ static gboolean mdy_topmost_window_pid_changed_cb(DBusMessage *const sig) { dbus_int32_t pid = -1; DBusError err = DBUS_ERROR_INIT; if( !dbus_message_get_args(sig, &err, DBUS_TYPE_INT32, &pid, DBUS_TYPE_INVALID) ) { mce_log(LL_WARN, "parse error: %s: %s", err.name, err.message); goto EXIT; } mce_log(LL_DEBUG, "received %s(%d)", COMPOSITOR_TOPMOST_WINDOW_PID_CHANGED, (int)pid); mdy_topmost_window_set_pid(pid); EXIT: return TRUE; } static DBusPendingCall *mdy_topmost_window_pid_pc = 0; /** Handle reply to pending compositor state request */ static void mdy_topmost_window_pid_reply_cb(DBusPendingCall *pc, void *aptr) { (void)aptr; DBusMessage *rsp = 0; DBusError err = DBUS_ERROR_INIT; dbus_int32_t pid = -1; if( mdy_topmost_window_pid_pc != pc ) goto EXIT; dbus_pending_call_unref(mdy_topmost_window_pid_pc), mdy_topmost_window_pid_pc = 0; mce_log(LL_NOTICE, "reply to %s()", COMPOSITOR_GET_TOPMOST_WINDOW_PID); if( !(rsp = dbus_pending_call_steal_reply(pc)) ) goto EXIT; if( dbus_set_error_from_message(&err, rsp) ) { mce_log(LL_WARN, "error reply: %s: %s", err.name, err.message); goto EXIT; } if( !dbus_message_get_args(rsp, &err, DBUS_TYPE_INT32, &pid, DBUS_TYPE_INVALID) ) { mce_log(LL_WARN, "parse error: %s: %s", err.name, err.message); goto EXIT; } mdy_topmost_window_set_pid(pid); EXIT: if( rsp ) dbus_message_unref(rsp); dbus_error_free(&err); return; } /** Abandon pending compositor state request */ static void mdy_topmost_window_forget_pid_query(void) { if( mdy_topmost_window_pid_pc ) { mce_log(LL_NOTICE, "forget %s()", COMPOSITOR_GET_TOPMOST_WINDOW_PID); dbus_pending_call_cancel(mdy_topmost_window_pid_pc); dbus_pending_call_unref(mdy_topmost_window_pid_pc), mdy_topmost_window_pid_pc = 0; } } /** Initiate async compositor state request */ static void mdy_topmost_window_send_pid_query(void) { mdy_topmost_window_forget_pid_query(); mce_log(LL_NOTICE, "call %s()", COMPOSITOR_GET_TOPMOST_WINDOW_PID); dbus_send_ex(COMPOSITOR_SERVICE, COMPOSITOR_PATH, COMPOSITOR_IFACE, COMPOSITOR_GET_TOPMOST_WINDOW_PID, mdy_topmost_window_pid_reply_cb, 0, 0, &mdy_topmost_window_pid_pc, DBUS_TYPE_INVALID); } /* ========================================================================= * * COMPOSITOR_LEDS * ========================================================================= */ /** Mapping for: compositor_led_t enumeration to led pattern name. */ static const char * const compositor_led_pattern[COMPOSITOR_LED_NUMOF] = { [COMPOSITOR_LED_ENABLE_PANIC] = MCE_LED_PATTERN_DISPLAY_UNBLANK_FAILED, [COMPOSITOR_LED_DISABLE_PANIC] = MCE_LED_PATTERN_DISPLAY_BLANK_FAILED, [COMPOSITOR_LED_KILLER_ACTIVE] = MCE_LED_PATTERN_KILLING_LIPSTICK, }; /** Bookkeeping for activated/deactivated compositor led patterns */ static bool compositor_led_active[COMPOSITOR_LED_NUMOF]; /** Activate/deactivate compositor related debug led pattern */ static void compositor_led_set_active(compositor_led_t led, bool active) { if( compositor_led_active[led] == active ) goto EXIT; mce_log(LL_DEBUG, "%s %s", active ? "activate" : "deactivate", compositor_led_pattern[led]); datapipe_exec_full((compositor_led_active[led] = active) ? &led_pattern_activate_pipe : &led_pattern_deactivate_pipe, compositor_led_pattern[led]); EXIT: return; } static bool compositor_led_is_active(compositor_led_t led) { return compositor_led_active[led]; } /* ========================================================================= * * renderer_state_t * ========================================================================= */ /** Convert renderer_state_t enumeration value to human readable string */ static const char * renderer_state_repr(renderer_state_t state) { const char *repr = "RENDERER_INVALID"; switch( state ) { case RENDERER_ERROR: repr = "RENDERER_ERROR"; break; case RENDERER_UNKNOWN: repr = "RENDERER_UNKNOWN"; break; case RENDERER_DISABLED: repr = "RENDERER_DISABLED"; break; case RENDERER_ENABLED: repr = "RENDERER_ENABLED"; break; default: break; } return repr; } /* ========================================================================= * * compositor_state_t * ========================================================================= */ /** Convert compositor_state_t enumeration value to human readable string */ static const char * compositor_state_repr(compositor_state_t state) { const char *repr = "COMPOSITOR_STATE_INVALID"; switch( state ) { case COMPOSITOR_STATE_INITIAL: repr = "COMPOSITOR_STATE_INITIAL"; break; case COMPOSITOR_STATE_FINAL: repr = "COMPOSITOR_STATE_FINAL"; break; case COMPOSITOR_STATE_STOPPED: repr = "COMPOSITOR_STATE_STOPPED"; break; case COMPOSITOR_STATE_STARTED: repr = "COMPOSITOR_STATE_STARTED"; break; case COMPOSITOR_STATE_REQUESTING: repr = "COMPOSITOR_STATE_REQUESTING"; break; case COMPOSITOR_STATE_SETUP: repr = "COMPOSITOR_STATE_SETUP"; break; case COMPOSITOR_STATE_GRANTED: repr = "COMPOSITOR_STATE_GRANTED"; break; case COMPOSITOR_STATE_FAILED: repr = "COMPOSITOR_STATE_FAILED"; break; default: break; } return repr; } /* ========================================================================= * * compositor_stm_t * ========================================================================= */ /** Delay [s] from setUpdatesEnabled() to attempting compositor core dump */ static gint mdy_compositor_core_delay = MCE_DEFAULT_LIPSTICK_CORE_DELAY; static guint mdy_compositor_core_delay_setting_id = 0; /* Delay [s] from attempting compositor core dump to killing compositor */ static gint mdy_compositor_kill_delay = 25; /* Delay [s] for verifying whether compositor did exit after kill attempt */ static gint mdy_compositor_bury_delay = 5; /** Bookkeeping for MCE <-> COMPOSITOR IPC state management */ struct compositor_stm_t { /** Compositor D-Bus IPC state * * Modify via compositor_stm_set_state() */ compositor_state_t csi_state; /** Suspend proof state re-evaluation timer */ mce_wltimer_t *csi_eval_state_tmr; /** Private name of the compositor D-Bus service owner * * Modify via compositor_stm_set_service_owner() */ gchar *csi_service_owner; /** Whether compositor was already running when mce started up * * Setup actions must be skipped in such cases */ bool csi_initial_owner; /** Private name of the previous compositor D-Bus service owner * * Access via: * compositor_stm_get_lingerer() * compositor_stm_set_lingerer() */ gchar *csi_lingering_owner; /** Timer id for ignoring previous compositor D-Bus service owner * * Used by: * compositor_stm_schedule_linger_timeout() * compositor_stm_cancel_linger_timeout() * compositor_stm_linger_timeout_cb() */ guint csi_linger_timeout_id; /** Process identifier of the compositor D-Bus service * * Modify via compositor_stm_set_service_pid() */ pid_t csi_service_pid; /** Bitmask of setup actions for compositor service */ unsigned csi_service_setup_actions; /** Actions to execute before enabling rendering */ unsigned csi_service_queued_actions; /** Action currently being executed in worker thread */ unsigned csi_service_pending_action; /** In what state we want the compositor to be in * * Modify via compositor_stm_set_target() */ renderer_state_t csi_target; /** In what state we have requested the compositor to be in * * Modify via compositor_stm_set_requested() */ renderer_state_t csi_requested; /** In what state we know the compositor to be in * * Modify via compositor_stm_set_granted() */ renderer_state_t csi_granted; /** Timer id for compositor method call retries * * Managed by compositor_stm_schedule_retry() & co */ guint csi_retry_timer_id; /** Delay between compositor method call retries [ms] * * Set in compositor_stm_ctor(), currently does not change during * runtime. */ guint csi_retry_delay; /** Currently pending compositor pid query * * Managed by compositor_stm_send_ctrl_request() & co */ DBusPendingCall *csi_pid_query_pc; /** Currently pending compositor setup actions query * * Managed by compositor_stm_send_actions_query() & co */ DBusPendingCall *csi_setup_actions_query_pc; /** Currently pending compositor D-Bus method call * * Managed by compositor_stm_send_ctrl_request() & co */ DBusPendingCall *csi_ctrl_request_pc; /** Timer id for killing unresponsive compositor process * * Managed by compositor_stm_schedule_killer() & co */ guint csi_kill_timer_id; /** Timer id for activating unresponsive compositor panic led * * Managed by compositor_stm_schedule_panic() & co */ guint csi_panic_timer_id; /** Timer delay for activating unresponsive compositor panic led * * Tweaked from compositor_stm_enter_state() */ guint csi_panic_delay; }; /* ------------------------------------------------------------------------- * * configurable options * ------------------------------------------------------------------------- */ static gchar *compositor_stm_hwc_stop = NULL; static gchar *compositor_stm_hwc_start = NULL; static gchar *compositor_stm_hwc_restart = NULL; static void compositor_stm_init_config(void) { compositor_stm_hwc_stop = mce_conf_get_string("Compositor", "StopHwCompositor", NULL); compositor_stm_hwc_start = mce_conf_get_string("Compositor", "StartHwCompositor", NULL); compositor_stm_hwc_restart = mce_conf_get_string("Compositor", "RestartHwCompositor", NULL); } static void compositor_stm_quit_config(void) { g_free(compositor_stm_hwc_stop), compositor_stm_hwc_stop = NULL; g_free(compositor_stm_hwc_start), compositor_stm_hwc_start = NULL; g_free(compositor_stm_hwc_restart), compositor_stm_hwc_restart = NULL; } /* ------------------------------------------------------------------------- * * construct/destruct * ------------------------------------------------------------------------- */ /** Initialize compositor_stm_t object */ static void compositor_stm_ctor(compositor_stm_t *self) { /* Waiting for dbus name owner probe to finish * i.e. compositor_stm_set_service_owner() to * be called. */ self->csi_state = COMPOSITOR_STATE_INITIAL; /* Setup syspend proof rethink timer */ self->csi_eval_state_tmr = mce_wltimer_create("compositor_state_rethink", 0, compositor_stm_eval_state_cb, self); /* compositor dbus service owner is not known */ self->csi_service_owner = 0; self->csi_initial_owner = false; self->csi_service_pid = COMPOSITOR_STM_INVALID_PID; self->csi_service_setup_actions = COMPOSITOR_ACTION_NONE; /* There is no lingering previous name owner */ self->csi_lingering_owner = 0; self->csi_linger_timeout_id = 0; /* On startup we want to enable compositor asap */ self->csi_target = RENDERER_ENABLED; self->csi_requested = RENDERER_UNKNOWN; self->csi_granted = RENDERER_UNKNOWN; /* No pending pid query */ self->csi_pid_query_pc = 0; /* No pending setup actions query */ self->csi_setup_actions_query_pc = 0; /* No pending compositor dbus method call */ self->csi_ctrl_request_pc = 0; /* Retry timer is inactive */ self->csi_retry_timer_id = 0; self->csi_retry_delay = COMPOSITOR_STM_DBUS_RETRY_DELAY; /* Compositor kill timer is inactive */ self->csi_kill_timer_id = 0; /* Delayer compositor reply panic timer is inactive */ self->csi_panic_timer_id = 0; self->csi_panic_delay = COMPOSITOR_STM_INITIAL_PANIC_DELAY; } /** Cancel pending actions and release dynamic resources */ static void compositor_stm_dtor(compositor_stm_t *self) { /* Cleanup happens via the state machine */ compositor_stm_set_state(self, COMPOSITOR_STATE_FINAL); } /* ------------------------------------------------------------------------- * * create/delete * ------------------------------------------------------------------------- */ /** Allocate and initialize compositor_stm_t object */ compositor_stm_t * compositor_stm_create(void) { compositor_stm_t *self = calloc(1, sizeof *self); compositor_stm_ctor(self); return self; } /** Finalize and release compositor_stm_t object */ void compositor_stm_delete(compositor_stm_t *self) { if( self != 0 ) { compositor_stm_dtor(self); free(self); } } /** Type agnostic delete callback for compositor_stm_t objects */ void compositor_stm_delete_cb(void *self) { compositor_stm_delete(self); } /* ------------------------------------------------------------------------- * * managing compositor name owner details * ------------------------------------------------------------------------- */ /** Store compositor dbus service process identifier */ static void compositor_stm_set_service_pid(compositor_stm_t *self, pid_t pid) { if( self->csi_service_pid != pid ) { mce_log(LL_DEBUG, "compositor_stm_service_pid: %d -> %d", (int)self->csi_service_pid, (int)pid); self->csi_service_pid = pid; } } /** Store compositor dbus service name owner */ static void compositor_stm_set_service_owner(compositor_stm_t *self, const char *owner) { if( owner && !*owner ) owner = 0; /* Special case: When mce starts up and compositor service is not yet * running we get "none-to-none" report. While that is not ownership * change, it must still xfer state machine away from the initial state. */ bool changed = g_strcmp0(self->csi_service_owner, owner) != 0; bool initial = (self->csi_state == COMPOSITOR_STATE_INITIAL); if( changed ) { mce_log(LL_DEBUG, "compositor_stm_service_owner: %s -> %s", self->csi_service_owner ?: "none", owner ?: "none"); g_free(self->csi_service_owner), self->csi_service_owner = g_strdup(owner); } if( self->csi_state == COMPOSITOR_STATE_FINAL ) goto EXIT; if( changed || initial ) { if( self->csi_service_owner ) compositor_stm_set_state(self, COMPOSITOR_STATE_STARTED); else compositor_stm_set_state(self, COMPOSITOR_STATE_STOPPED); } EXIT: return; } /** Get compositor dbus service name owner */ static const char * compositor_stm_get_service_owner(const compositor_stm_t *self) { return self->csi_service_owner; } /* ------------------------------------------------------------------------- * * managing org.freedesktop.DBus.GetConnectionUnixProcessID() method call * ------------------------------------------------------------------------- */ /* Update service setup actions bookkeeping */ static void compositor_stm_set_service_actions(compositor_stm_t *self, unsigned actions) { if( self->csi_service_setup_actions != actions ) { mce_log(LL_DEBUG, "compositor_stm_service_actions: 0x%x -> 0x%x", self->csi_service_setup_actions, actions); self->csi_service_setup_actions = actions; } } /* Predicate for: pending service pid query */ static bool compositor_stm_pending_pid_query(const compositor_stm_t *self) { return self->csi_pid_query_pc != NULL; } /** Initiate async query to find out pid of compositor dbus service */ static void compositor_stm_send_pid_query(compositor_stm_t *self) { compositor_stm_forget_pid_query(self); mce_log(LL_NOTICE, "start pid query"); dbus_send_ex(DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "GetConnectionUnixProcessID", // ---------------- compositor_stm_pid_query_cb, self, 0, &self->csi_pid_query_pc, // ---------------- DBUS_TYPE_STRING, &self->csi_service_owner, DBUS_TYPE_INVALID); } /** Abandon pending compositor dbus service pid query */ static void compositor_stm_forget_pid_query(compositor_stm_t *self) { if( self->csi_pid_query_pc ) { mce_log(LL_NOTICE, "forget pid query"); dbus_pending_call_cancel(self->csi_pid_query_pc); dbus_pending_call_unref(self->csi_pid_query_pc), self->csi_pid_query_pc = 0; } } /** Handle reply to pending compositor dbus service pid query */ static void compositor_stm_pid_query_cb(DBusPendingCall *pc, void *aptr) { compositor_stm_t *self = aptr; DBusMessage *rsp = 0; DBusError err = DBUS_ERROR_INIT; dbus_uint32_t dta = 0; pid_t pid = COMPOSITOR_STM_INVALID_PID; if( self->csi_pid_query_pc != pc ) goto EXIT; dbus_pending_call_unref(self->csi_pid_query_pc), self->csi_pid_query_pc = 0; if( !(rsp = dbus_pending_call_steal_reply(pc)) ) goto EXIT; mce_log(LL_NOTICE, "reply to pid query"); if( dbus_set_error_from_message(&err, rsp) || !dbus_message_get_args(rsp, &err, DBUS_TYPE_UINT32, &dta, DBUS_TYPE_INVALID) ) { mce_log(LL_ERR, "%s: %s", err.name, err.message); } else { pid = (pid_t)dta; } compositor_stm_set_service_pid(self, pid); compositor_stm_eval_state(self); EXIT: if( rsp ) dbus_message_unref(rsp); dbus_error_free(&err); return; } /** Execute a command helper function */ static int compositor_stm_execute(const char *command) { int result = -1; int status = -1; char exited[32] = ""; char trapped[32] = ""; const char *dumped = ""; mce_log(LL_DEBUG, "EXEC %s", command); fflush(NULL); if( (status = system(command)) == -1 ) { snprintf(exited, sizeof exited, " exec=failed"); } else { if( WIFSIGNALED(status) ) { snprintf(trapped, sizeof trapped, " signal=%s", strsignal(WTERMSIG(status))); } if( WCOREDUMP(status) ) dumped = " core=dumped"; if( WIFEXITED(status) ) { result = WEXITSTATUS(status); snprintf(exited, sizeof exited, " exit_code=%d", result); } } if( result != 0 ) { mce_log(LL_WARN, "EXEC %s; %s%s%s result=%d", command, exited, trapped, dumped, result); } return result; } /** Worker thread callback for executing actions */ static void *compositor_stm_action_exec_cb(void *aptr) { mce_log(LL_DEBUG, "execute action at worker thread"); /* Note: This is executed in the worker thread context */ compositor_stm_t *self = aptr; const char *command = NULL; unsigned action = (self->csi_service_pending_action & self->csi_service_queued_actions); switch( action ) { case COMPOSITOR_ACTION_STOP_HWC: command = compositor_stm_hwc_stop; break; case COMPOSITOR_ACTION_START_HWC: command = compositor_stm_hwc_start; break; case COMPOSITOR_ACTION_RESTART_HWC: command = compositor_stm_hwc_restart; break; default: mce_log(LL_WARN, "hwc action execution out of sync"); break; } if( command ) compositor_stm_execute(command); return GINT_TO_POINTER(action); } /** Worker thread finished notification callback */ static void compositor_stm_action_done_cb(void *aptr, void *reply) { mce_log(LL_DEBUG, "action executed by worker thread"); compositor_stm_t *self = aptr; unsigned action = GPOINTER_TO_UINT(reply); if( self->csi_service_pending_action != action ) { mce_log(LL_WARN, "hwc action execution out of sync"); } else { mce_log(LL_DEBUG, "pending hwc action done"); self->csi_service_queued_actions &= ~action; self->csi_service_pending_action = COMPOSITOR_ACTION_NONE; } compositor_stm_eval_state(self); } /* ------------------------------------------------------------------------- * * managing org.nemomobile.compositor.privateGetSetupActions method call * ------------------------------------------------------------------------- */ /** Predicate for: pending service setup actions query */ static bool compositor_stm_pending_actions_query(const compositor_stm_t *self) { return self->csi_setup_actions_query_pc != NULL; } /** Initiate async query to find out setup actions for compositor dbus service */ static void compositor_stm_send_actions_query(compositor_stm_t *self) { compositor_stm_forget_actions_query(self); mce_log(LL_NOTICE, "start setup actions query"); dbus_send_ex(COMPOSITOR_SERVICE, COMPOSITOR_PATH, COMPOSITOR_IFACE, COMPOSITOR_GET_SETUP_ACTIONS, // ---------------- compositor_stm_actions_query_cb, self, 0, &self->csi_setup_actions_query_pc, // ---------------- DBUS_TYPE_INVALID); } /** Abandon pending compositor dbus service setup actions query */ static void compositor_stm_forget_actions_query(compositor_stm_t *self) { if( self->csi_setup_actions_query_pc ) { mce_log(LL_NOTICE, "forget setup actions query"); dbus_pending_call_cancel(self->csi_setup_actions_query_pc); dbus_pending_call_unref(self->csi_setup_actions_query_pc), self->csi_setup_actions_query_pc = 0; } } /** Handle reply to pending compositor dbus service setup actions query */ static void compositor_stm_actions_query_cb(DBusPendingCall *pc, void *aptr) { compositor_stm_t *self = aptr; DBusMessage *rsp = 0; DBusError err = DBUS_ERROR_INIT; dbus_uint32_t dta = 0; unsigned actions = COMPOSITOR_ACTION_NONE; if( self->csi_setup_actions_query_pc != pc ) goto BAILOUT; mce_log(LL_NOTICE, "reply to setup actions query"); dbus_pending_call_unref(self->csi_setup_actions_query_pc), self->csi_setup_actions_query_pc = 0; if( !(rsp = dbus_pending_call_steal_reply(pc)) ) goto UPDATE; if( dbus_set_error_from_message(&err, rsp) || !dbus_message_get_args(rsp, &err, DBUS_TYPE_UINT32, &dta, DBUS_TYPE_INVALID) ) { mce_log(LL_ERR, "%s: %s", err.name, err.message); } else { actions = (unsigned)dta; } UPDATE: compositor_stm_set_service_actions(self, actions); compositor_stm_eval_state(self); BAILOUT: if( rsp ) dbus_message_unref(rsp); dbus_error_free(&err); return; } /* ------------------------------------------------------------------------- * * managing org.nemomobile.compositor.setUpdatesEnabled() method calls * ------------------------------------------------------------------------- */ /** Initiate async compositor state request */ static void compositor_stm_send_ctrl_request(compositor_stm_t *self) { compositor_stm_forget_ctrl_request(self); dbus_bool_t dta = (self->csi_requested == RENDERER_ENABLED); mce_log(LL_NOTICE, "call %s(%s)", COMPOSITOR_SET_UPDATES_ENABLED, renderer_state_repr(self->csi_requested)); // XXX we want to use longer than default timeout here! bool ack = dbus_send_ex2(COMPOSITOR_SERVICE, COMPOSITOR_PATH, COMPOSITOR_IFACE, COMPOSITOR_SET_UPDATES_ENABLED, compositor_stm_ctrl_request_cb, COMPOSITOR_STM_DBUS_CALL_TIMEOUT, self, 0, &self->csi_ctrl_request_pc, DBUS_TYPE_BOOLEAN, &dta, DBUS_TYPE_INVALID); if( !ack ) compositor_stm_set_state(self, COMPOSITOR_STATE_FAILED); } /** Abandon pending compositor state request */ static void compositor_stm_forget_ctrl_request(compositor_stm_t *self) { if( self->csi_ctrl_request_pc ) { mce_log(LL_NOTICE, "forget %s(%s)", COMPOSITOR_SET_UPDATES_ENABLED, renderer_state_repr(self->csi_requested)); dbus_pending_call_cancel(self->csi_ctrl_request_pc); dbus_pending_call_unref(self->csi_ctrl_request_pc), self->csi_ctrl_request_pc = 0; } } /** Handle reply to pending compositor state request */ static void compositor_stm_ctrl_request_cb(DBusPendingCall *pc, void *aptr) { compositor_stm_t *self = aptr; DBusMessage *rsp = 0; DBusError err = DBUS_ERROR_INIT; bool ack = false; if( self->csi_ctrl_request_pc != pc ) goto EXIT; dbus_pending_call_unref(self->csi_ctrl_request_pc), self->csi_ctrl_request_pc = 0; mce_log(LL_NOTICE, "reply to %s(%s)", COMPOSITOR_SET_UPDATES_ENABLED, renderer_state_repr(self->csi_requested)); if( !(rsp = dbus_pending_call_steal_reply(pc)) ) goto EXIT; if( dbus_set_error_from_message(&err, rsp) ) { mce_log(LL_WARN, "%s: %s", err.name, err.message); goto EXIT; } ack = true; EXIT: if( ack ) compositor_stm_set_state(self, COMPOSITOR_STATE_GRANTED); else compositor_stm_set_state(self, COMPOSITOR_STATE_FAILED); if( rsp ) dbus_message_unref(rsp); dbus_error_free(&err); return; } /* ------------------------------------------------------------------------- * * panic led activation * ------------------------------------------------------------------------- */ /** Schedule actiovation of unresponsive-compositor panic led pattern */ static void compositor_stm_schedule_panic(compositor_stm_t *self) { /* Skip if timer already scheduled */ if( self->csi_panic_timer_id ) goto EXIT; /* Skip if panic led pattern is already active */ switch( self->csi_requested ) { case RENDERER_ENABLED: if( compositor_led_is_active(COMPOSITOR_LED_ENABLE_PANIC) ) goto EXIT; break; case RENDERER_DISABLED: if( compositor_led_is_active(COMPOSITOR_LED_DISABLE_PANIC) ) goto EXIT; default: break; } mce_log(LL_DEBUG, "schedule panic led"); self->csi_panic_timer_id = g_timeout_add(self->csi_panic_delay, compositor_stm_panic_timer_cb, self); EXIT: return; } /** Cancel actiovation of unresponsive-compositor panic led pattern */ static void compositor_stm_cancel_panic(compositor_stm_t *self) { compositor_led_set_active(COMPOSITOR_LED_ENABLE_PANIC, false); compositor_led_set_active(COMPOSITOR_LED_DISABLE_PANIC, false); if( !self->csi_panic_timer_id ) goto EXIT; mce_log(LL_DEBUG, "cancel panic led"); g_source_remove(self->csi_panic_timer_id), self->csi_panic_timer_id = 0; EXIT: return; } /** Trigger actiovation of unresponsive-compositor panic led pattern */ static gboolean compositor_stm_panic_timer_cb(void *aptr) { compositor_stm_t *self = aptr; if( !self->csi_panic_timer_id ) goto EXIT; self->csi_panic_timer_id = 0; mce_log(LL_WARN, "panic led triggered"); switch( self->csi_requested ) { case RENDERER_ENABLED: compositor_led_set_active(COMPOSITOR_LED_ENABLE_PANIC, true); compositor_led_set_active(COMPOSITOR_LED_DISABLE_PANIC, false); break; case RENDERER_DISABLED: compositor_led_set_active(COMPOSITOR_LED_DISABLE_PANIC, true); compositor_led_set_active(COMPOSITOR_LED_ENABLE_PANIC, false); break; default: break; } EXIT: return FALSE; } /* ------------------------------------------------------------------------- * * retry timer * ------------------------------------------------------------------------- */ /** Schedule retrying of failed compositor state request */ static void compositor_stm_schedule_retry(compositor_stm_t *self) { compositor_stm_cancel_retry(self); mce_log(LL_DEBUG, "schedule ipc retry"); self->csi_retry_timer_id = g_timeout_add(self->csi_retry_delay, compositor_stm_retry_timer_cb, self); } /** Cancel retrying of failed compositor state request */ static void compositor_stm_cancel_retry(compositor_stm_t *self) { if( self->csi_retry_timer_id ) { mce_log(LL_DEBUG, "cancel ipc retry"); g_source_remove(self->csi_retry_timer_id), self->csi_retry_timer_id = 0; } } /** Trigger retrying of failed compositor state request */ static gboolean compositor_stm_retry_timer_cb(void *aptr) { compositor_stm_t *self = aptr; if( !self->csi_retry_timer_id ) goto EXIT; self->csi_retry_timer_id = 0; mce_log(LL_DEBUG, "ipc retry triggered"); compositor_stm_set_state(self, COMPOSITOR_STATE_REQUESTING); EXIT: return FALSE; } /* ------------------------------------------------------------------------- * * compositor killer activation * ------------------------------------------------------------------------- */ /** Schedule killing of unresponsive compositor process */ static void compositor_stm_schedule_killer(compositor_stm_t *self) { /* Once scheduled, the kill timer is not reprogrammed until * the process is gone */ if( self->csi_kill_timer_id ) goto EXIT; /* The compositor killing is not used unless we have "devel" flavor * mce, or normal mce running in verbose mode */ if( !mce_log_p(LL_DEVEL) ) goto EXIT; /* Setting the core dump delay to zero disables killing too. */ if( mdy_compositor_core_delay <= 0 ) goto EXIT; mce_log(LL_DEBUG, "schedule compositor killer"); self->csi_kill_timer_id = g_timeout_add(mdy_compositor_core_delay * 1000, compositor_stm_core_timer_cb, self); EXIT: return; } /** Cancel killing of unresponsive compositor process */ static void compositor_stm_cancel_killer(compositor_stm_t *self) { compositor_led_set_active(COMPOSITOR_LED_KILLER_ACTIVE, false); if( !self->csi_kill_timer_id ) goto EXIT; mce_log(LL_DEBUG, "cancel compositor killer"); g_source_remove(self->csi_kill_timer_id), self->csi_kill_timer_id = 0; EXIT: return; } /** Trigger attempt to coredump unresponsive compositor process */ static gboolean compositor_stm_core_timer_cb(void *aptr) { compositor_stm_t *self = aptr; if( !self->csi_kill_timer_id ) goto EXIT; self->csi_kill_timer_id = 0; mce_log(LL_WARN, "compositor core dump triggered"); if( self->csi_service_pid == COMPOSITOR_STM_INVALID_PID ) goto EXIT; /* We do not want to kill compositor if debugger is attached to it. * Since there can be only one attacher at one time, we can use dummy * attach + detach cycle to determine debugger presence. */ if( ptrace(PTRACE_ATTACH, self->csi_service_pid, 0, 0) == -1 ) { mce_log(LL_WARN, "could not attach to compositor: %m"); mce_log(LL_WARN, "assuming debugger is attached; skip killing"); goto EXIT; } if( ptrace(PTRACE_DETACH, self->csi_service_pid, 0,0) == -1 ) { mce_log(LL_WARN, "could not detach from compositor: %m"); } /* Activate killer led - will be deactivated when compositor * restart is seen base on D-Bus name ownership. */ compositor_led_set_active(COMPOSITOR_LED_KILLER_ACTIVE, true); /* We need to send some signal that a) leads to core dump b) is not * handled "nicely" by compositor. SIGXCPU fits that description and * is also c) somewhat relevant "CPU time limit exceeded" d) easily * distinguishable from other "normal" crash reports. */ if( kill(self->csi_service_pid, SIGXCPU) == -1 ) { if( errno == ESRCH ) goto EXIT; mce_log(LL_WARN, "could not SIGXCPU compositor: %m"); } self->csi_kill_timer_id = g_timeout_add(mdy_compositor_kill_delay * 1000, compositor_stm_kill_timer_cb, self); EXIT: return FALSE; } /** Trigger attempt to kill unresponsive compositor process */ static gboolean compositor_stm_kill_timer_cb(void *aptr) { compositor_stm_t *self = aptr; if( !self->csi_kill_timer_id ) goto EXIT; self->csi_kill_timer_id = 0; mce_log(LL_WARN, "compositor kill triggered"); if( self->csi_service_pid == COMPOSITOR_STM_INVALID_PID ) goto EXIT; if( kill(self->csi_service_pid, SIGKILL) == -1 ) { if( errno == ESRCH ) goto EXIT; mce_log(LL_WARN, "could not SIGKILL compositor: %m"); } self->csi_kill_timer_id = g_timeout_add(mdy_compositor_bury_delay * 1000, compositor_stm_bury_timer_cb, self); EXIT: return FALSE; } /** Trigger a check if unresponsive compositor got killed */ static gboolean compositor_stm_bury_timer_cb(void *aptr) { compositor_stm_t *self = aptr; if( !self->csi_kill_timer_id ) goto EXIT; self->csi_kill_timer_id = 0; mce_log(LL_WARN, "compositor bury triggered"); if( self->csi_service_pid == COMPOSITOR_STM_INVALID_PID ) goto EXIT; if( kill(self->csi_service_pid, 0) == -1 && errno != ESRCH ) mce_log(LL_ERR, "compositor is not responsive and killing it failed"); EXIT: return FALSE; } /* ------------------------------------------------------------------------- * * manage waiting for previous compositor name owner to exit * ------------------------------------------------------------------------- */ /** Timer callback for: lingering compositor timeout * * Stop waiting for the previous compositor to exit and * and yield control to the current one. */ static gboolean compositor_stm_linger_timeout_cb(gpointer aptr) { compositor_stm_t *self = aptr; self->csi_linger_timeout_id = 0; mce_log(LL_DEBUG, "linger timeout triggered"); // forget compositor we have done ipc with compositor_stm_set_lingerer(self, 0); return G_SOURCE_REMOVE; } /** Cancel scheduled lingering compositor timeout */ static void compositor_stm_cancel_linger_timeout(compositor_stm_t *self) { if( self->csi_linger_timeout_id ) { mce_log(LL_DEBUG, "linger timeout canceled"); g_source_remove(self->csi_linger_timeout_id), self->csi_linger_timeout_id = 0; } } /** Schedule lingering compositor timeout */ static void compositor_stm_schedule_linger_timeout(compositor_stm_t *self) { compositor_stm_cancel_linger_timeout(self); mce_log(LL_DEBUG, "linger timeout scheduled"); self->csi_linger_timeout_id = g_timeout_add(5000, compositor_stm_linger_timeout_cb, self); } /** Peerinfo notification callback for lingering compositor * * Once the previous compositor makes an exit (it is assumed * that dropping out of system bus is good enough approximation) * we can yield control to a new / waiting compositor instance. */ static void compositor_stm_lingerer_info_cb(const peerinfo_t *peerinfo, gpointer aptr) { compositor_stm_t *self = aptr; const char *name = peerinfo_name(peerinfo); pid_t pid = peerinfo_get_owner_pid(peerinfo); peerstate_t state = peerinfo_get_state(peerinfo); mce_log(LL_DEBUG, "lingering compositor: name=%s pid=%d state=%s", name, (int)pid, peerstate_repr(state)); if( state == PEERSTATE_STOPPED ) { if( !g_strcmp0(compositor_stm_get_lingerer(self), name) ) compositor_stm_set_lingerer(self, 0); } } /** Get private name of lingering compositor service */ static const char * compositor_stm_get_lingerer(const compositor_stm_t *self) { return self->csi_lingering_owner; } /** Set private name of lingering compositor service * * Should be called as soon as a compositor instance has been * accepted as target of compositor dbus ipc. */ static void compositor_stm_set_lingerer(compositor_stm_t *self, const char *name) { if( !g_strcmp0(self->csi_lingering_owner, name) ) goto EXIT; if( self->csi_lingering_owner ) { mce_log(LL_DEBUG, "lingering compositor: name=%s - ignored", self->csi_lingering_owner); mce_dbus_name_tracker_remove(self->csi_lingering_owner, compositor_stm_lingerer_info_cb, self); g_free(self->csi_lingering_owner), self->csi_lingering_owner = 0; compositor_stm_cancel_linger_timeout(self); } if( name ) { self->csi_lingering_owner = g_strdup(name); mce_log(LL_DEBUG, "lingering compositor: name=%s - tracked", self->csi_lingering_owner); mce_dbus_name_tracker_add(self->csi_lingering_owner, compositor_stm_lingerer_info_cb, self, 0); } compositor_stm_eval_state(self); EXIT: return; } /* ------------------------------------------------------------------------- * * compositor ipc state machine * ------------------------------------------------------------------------- */ /** Get current state of the state machine */ static compositor_state_t compositor_stm_get_state(const compositor_stm_t *self) { return self->csi_state; } /** Perform a state transition */ static void compositor_stm_set_state(compositor_stm_t *self, compositor_state_t state) { if( self->csi_state == COMPOSITOR_STATE_FINAL ) { mce_log(LL_ERR, "compositor_stm_state: %s -> %s - rejected", compositor_state_repr(self->csi_state), compositor_state_repr(state)); goto EXIT; } if( self->csi_state == state ) goto EXIT; mce_log(LL_DEBUG, "compositor_stm_state: %s -> %s", compositor_state_repr(self->csi_state), compositor_state_repr(state)); compositor_stm_leave_state(self); self->csi_state = state; compositor_stm_enter_state(self); EXIT: compositor_stm_eval_state(self); return; } /** Perform actions on entry to a state */ static void compositor_stm_enter_state(compositor_stm_t *self) { mce_log(LL_DEBUG, "ENTER %s - %s", compositor_state_repr(self->csi_state), mce_dbus_get_name_owner_ident(self->csi_service_owner)); switch( self->csi_state ) { case COMPOSITOR_STATE_INITIAL: break; case COMPOSITOR_STATE_FINAL: // delete rethink timer mce_wltimer_delete(self->csi_eval_state_tmr), self->csi_eval_state_tmr = NULL; // forget the service name/pid compositor_stm_set_service_owner(self, 0); // cancel all pending actions compositor_stm_cancel_retry(self); compositor_stm_cancel_panic(self); compositor_stm_cancel_killer(self); compositor_stm_forget_ctrl_request(self); compositor_stm_forget_pid_query(self); compositor_stm_forget_actions_query(self); // deactivate all led patterns for( compositor_led_t led = 0; led < COMPOSITOR_LED_NUMOF; ++led ) compositor_led_set_active(led, false); // clear previous compositor tracking data compositor_stm_set_lingerer(self, 0); break; case COMPOSITOR_STATE_STOPPED: compositor_stm_forget_pid_query(self); compositor_stm_cancel_killer(self); compositor_stm_cancel_panic(self); // leave via compositor_stm_set_service_owner() /* Wake display state machine */ mdy_stm_schedule_rethink(); break; case COMPOSITOR_STATE_STARTED: // apply timeout for waiting previous compositor to exit if( compositor_stm_get_lingerer(self) ) compositor_stm_schedule_linger_timeout(self); self->csi_panic_delay = COMPOSITOR_STM_INITIAL_PANIC_DELAY; compositor_stm_send_pid_query(self); compositor_stm_send_actions_query(self); break; case COMPOSITOR_STATE_SETUP: // remember compositor we have done ipc with compositor_stm_set_lingerer(self, self->csi_service_owner); if( self->csi_initial_owner ) { mce_log(LL_DEBUG, "Actions skipped for already running compositor"); self->csi_initial_owner = false; } else { unsigned actions = self->csi_service_setup_actions; if( actions & COMPOSITOR_ACTION_RESTART_HWC ) { mce_log(LL_DEBUG, "Action: RestartpHwCompositor"); if( !compositor_stm_hwc_restart ) { /* Restart command is not defined, fallback: stop + start */ actions |= (COMPOSITOR_ACTION_STOP_HWC | COMPOSITOR_ACTION_START_HWC); } else { /* And mask out stop / start actions */ actions &= ~(COMPOSITOR_ACTION_STOP_HWC | COMPOSITOR_ACTION_START_HWC); } } if( !compositor_stm_hwc_stop ) actions &= ~COMPOSITOR_ACTION_STOP_HWC; if( !compositor_stm_hwc_start ) actions &= ~COMPOSITOR_ACTION_START_HWC; self->csi_service_pending_action = COMPOSITOR_ACTION_NONE; self->csi_service_queued_actions = actions; } break; case COMPOSITOR_STATE_REQUESTING: compositor_stm_set_requested(self, self->csi_target); compositor_stm_schedule_killer(self); compositor_stm_schedule_panic(self); compositor_stm_send_ctrl_request(self); break; case COMPOSITOR_STATE_GRANTED: self->csi_panic_delay = self->csi_panic_delay * 3 / 4; if( self->csi_panic_delay < COMPOSITOR_STM_MINIMUM_PANIC_DELAY ) self->csi_panic_delay = COMPOSITOR_STM_MINIMUM_PANIC_DELAY; compositor_stm_set_granted(self, self->csi_requested); compositor_stm_cancel_killer(self); compositor_stm_cancel_panic(self); /* Wake display state machine */ mdy_stm_schedule_rethink(); break; case COMPOSITOR_STATE_FAILED: compositor_stm_set_granted(self, RENDERER_ERROR); compositor_stm_schedule_retry(self); /* We can't enter suspend without UI side being in * the loop or we'll risk spectacular crashes */ mdy_stm_schedule_rethink(); break; default: break; } } /** Perform actions when leaving a state */ static void compositor_stm_leave_state(compositor_stm_t *self) { mce_log(LL_DEBUG, "LEAVE %s - %s", compositor_state_repr(self->csi_state), mce_dbus_get_name_owner_ident(self->csi_service_owner)); switch( self->csi_state ) { case COMPOSITOR_STATE_INITIAL: if( self->csi_service_owner ) { mce_log(LL_DEBUG, "Compositor already running"); self->csi_initial_owner = true; } break; case COMPOSITOR_STATE_FINAL: break; case COMPOSITOR_STATE_STOPPED: break; case COMPOSITOR_STATE_STARTED: compositor_stm_cancel_linger_timeout(self); compositor_stm_forget_pid_query(self); compositor_stm_forget_actions_query(self); break; case COMPOSITOR_STATE_SETUP: /* Clear action queue, so that any possibly left behind * asynchronously executed worker task has a chance to * do nothing when it gets scheduled. */ self->csi_service_pending_action = COMPOSITOR_ACTION_NONE; self->csi_service_queued_actions = COMPOSITOR_ACTION_NONE; break; case COMPOSITOR_STATE_REQUESTING: compositor_stm_forget_ctrl_request(self); break; case COMPOSITOR_STATE_GRANTED: compositor_stm_set_granted(self, RENDERER_UNKNOWN); /* Wake display state machine */ mdy_stm_schedule_rethink(); break; case COMPOSITOR_STATE_FAILED: compositor_stm_cancel_retry(self); break; default: break; } } /** Check if actions need to be taken within a state */ static gboolean compositor_stm_eval_state_cb(gpointer aptr) { compositor_stm_t *self = aptr; mce_log(LL_DEBUG, "EVAL %s - %s", compositor_state_repr(self->csi_state), mce_dbus_get_name_owner_ident(self->csi_service_owner)); /* Evaluate "pending" status on entry */ bool was_pending = compositor_stm_is_pending(self); /* Default to: timer will be deactivated */ mce_wltimer_stop(self->csi_eval_state_tmr); switch( self->csi_state ) { case COMPOSITOR_STATE_INITIAL: break; case COMPOSITOR_STATE_FINAL: break; case COMPOSITOR_STATE_STOPPED: break; case COMPOSITOR_STATE_STARTED: /* Proceed once lingering compositor has exited / is ignored */ if( compositor_stm_pending_pid_query(self) ) { mce_log(LL_DEBUG, "pending pid query ..."); break; } if( compositor_stm_pending_actions_query(self) ) { mce_log(LL_DEBUG, "pending setup actions query ..."); break; } if( compositor_stm_get_lingerer(self) ) { mce_log(LL_DEBUG, "pending service linger ..."); break; } compositor_stm_set_state(self, COMPOSITOR_STATE_SETUP); break; case COMPOSITOR_STATE_SETUP: /* Still executing something? */ if( self->csi_service_pending_action ) { mce_log(LL_DEBUG, "pending actions ..."); break; } /* Still have something to execute? */ if( self->csi_service_queued_actions & COMPOSITOR_ACTION_STOP_HWC ) { mce_log(LL_DEBUG, "scheduling stop action ..."); self->csi_service_pending_action = COMPOSITOR_ACTION_STOP_HWC; } else if( self->csi_service_queued_actions & COMPOSITOR_ACTION_START_HWC ) { mce_log(LL_DEBUG, "scheduling start action ..."); self->csi_service_pending_action = COMPOSITOR_ACTION_START_HWC; } else if( self->csi_service_queued_actions & COMPOSITOR_ACTION_RESTART_HWC ) { mce_log(LL_DEBUG, "scheduling restart action ..."); self->csi_service_pending_action = COMPOSITOR_ACTION_RESTART_HWC; } if( self->csi_service_pending_action ) { mce_worker_add_job(MODULE_NAME, "hwc-action", compositor_stm_action_exec_cb, compositor_stm_action_done_cb, self); break; } /* All done, proceed */ compositor_stm_set_state(self, COMPOSITOR_STATE_REQUESTING); break; case COMPOSITOR_STATE_REQUESTING: break; case COMPOSITOR_STATE_GRANTED: /* React to changes via compositor_stm_set_enabled() */ if( self->csi_target != self->csi_granted ) compositor_stm_set_state(self, COMPOSITOR_STATE_REQUESTING); break; case COMPOSITOR_STATE_FAILED: break; default: break; } /* If "pending" status changed, wake up display state machine */ if( compositor_stm_is_pending(self) != was_pending ) mdy_stm_schedule_rethink(); return G_SOURCE_REMOVE; } /** Schedule compositor state machine state re-evaluation */ static void compositor_stm_eval_state(compositor_stm_t *self) { mce_wltimer_start(self->csi_eval_state_tmr); } /* ------------------------------------------------------------------------- * * managing target/requested/granted states * ------------------------------------------------------------------------- */ /** Set the state we want compositor side to be in */ static void compositor_stm_set_target(compositor_stm_t *self, renderer_state_t state) { /* Only RENDERER_ENABLED or RENDERER_DISABLED can be target */ if( state != RENDERER_DISABLED ) state = RENDERER_ENABLED; if( self->csi_target != state ) { mce_log(LL_DEBUG, "compositor_stm_target: %s -> %s", renderer_state_repr(self->csi_target), renderer_state_repr(state)); self->csi_target = state; } } /** Set the state we have requested compositor to transfer to */ static void compositor_stm_set_requested(compositor_stm_t *self, renderer_state_t state) { if( self->csi_requested != state ) { mce_log(LL_DEBUG, "compositor_stm_requested: %s -> %s", renderer_state_repr(self->csi_requested), renderer_state_repr(state)); self->csi_requested = state; } } /** Set the state compositor has transferred to */ static void compositor_stm_set_granted(compositor_stm_t *self, renderer_state_t state) { if( self->csi_granted != state ) { mce_log(LL_DEBUG, "compositor_stm_granted: %s -> %s", renderer_state_repr(self->csi_granted), renderer_state_repr(state)); self->csi_granted = state; } } /* ------------------------------------------------------------------------- * * external interface for use from display state machine * ------------------------------------------------------------------------- */ /** Predicate for: compositor side state is not known */ static bool compositor_stm_is_pending(const compositor_stm_t *self) { bool pending = true; switch( compositor_stm_get_state(self) ) { case COMPOSITOR_STATE_GRANTED: case COMPOSITOR_STATE_STOPPED: if( !mce_wltimer_is_active(self->csi_eval_state_tmr) ) pending = false; break; default: break; } return pending; } /** Predicate for: compositor side is in setUpdatesEnabled(true) state */ static bool compositor_stm_is_enabled(const compositor_stm_t *self) { if( compositor_stm_is_pending(self) ) return false; return self->csi_granted == RENDERER_ENABLED; } /** Predicate for: compositor side is in setUpdatesEnabled(false) state */ static bool compositor_stm_is_disabled(const compositor_stm_t *self) { if( compositor_stm_is_pending(self) ) return false; return self->csi_granted == RENDERER_DISABLED; } /** Change state we want compositor side to be in */ static void compositor_stm_set_enabled(compositor_stm_t *self, bool enable) { if( enable ) compositor_stm_set_target(self, RENDERER_ENABLED); else compositor_stm_set_target(self, RENDERER_DISABLED); compositor_stm_eval_state(self); } /* ========================================================================= * * COMPOSITOR_IPC * ========================================================================= */ static compositor_stm_t *mdy_compositor_ipc = 0; /** Start compositor D-Bus ipc state tracking */ static void mdy_compositor_init(void) { if( !mdy_compositor_ipc ) mdy_compositor_ipc = compositor_stm_create(); } /** Stop compositor D-Bus ipc state tracking */ static void mdy_compositor_quit(void) { compositor_stm_delete(mdy_compositor_ipc), mdy_compositor_ipc = 0; } /** Set owner of compositor D-Bus service * * @param curr Private name of the service owner, or empty/null for none */ static void mdy_compositor_set_name_owner(const char *curr) { if( mdy_compositor_ipc ) compositor_stm_set_service_owner(mdy_compositor_ipc, curr); } /* Start setUpdatesEnabled(true) ipc with systemui */ static void mdy_compositor_enable(void) { if( mdy_compositor_ipc ) compositor_stm_set_enabled(mdy_compositor_ipc, true); } /* Start setUpdatesEnabled(false) ipc with systemui */ static void mdy_compositor_disable(void) { if( mdy_compositor_ipc ) compositor_stm_set_enabled(mdy_compositor_ipc, false); } /** Predicate for compositor D-Bus service is available */ static bool mdy_compositor_is_available(void) { bool available = false; if( mdy_compositor_ipc ) available = compositor_stm_get_service_owner(mdy_compositor_ipc) != 0; return available; } /** Predicate for setUpdatesEnabled() ipc not finished yet */ static bool mdy_compositor_is_pending(void) { bool pending = false; if( mdy_compositor_ipc ) pending = compositor_stm_is_pending(mdy_compositor_ipc); return pending; } /** Predicate for setUpdatesEnabled(false) ipc successfully finished */ static bool mdy_compositor_is_disabled(void) { bool disabled = false; if( mdy_compositor_ipc ) disabled = compositor_stm_is_disabled(mdy_compositor_ipc); return disabled; } /** Predicate for setUpdatesEnabled(true) ipc successfully finished */ static bool mdy_compositor_is_enabled(void) { bool enabled = false; if( mdy_compositor_ipc ) enabled = compositor_stm_is_enabled(mdy_compositor_ipc); return enabled; } /* ========================================================================= * * CALLSTATE_CHANGES * ========================================================================= */ /** Timer id for ending call state was recently changed condition */ static guint mdy_callstate_end_changed_id = 0; /** Timer callback for ending call state was recently changed condition */ static gboolean mdy_callstate_end_changed_cb(gpointer aptr) { (void)aptr; if( !mdy_callstate_end_changed_id ) goto EXIT; mdy_callstate_end_changed_id = 0; mce_log(LL_DEBUG, "suspend blocking/call state change: ended"); // autosuspend policy mdy_stm_schedule_rethink(); EXIT: return FALSE; } /** Predicate function for call state was recently changed */ static bool mdy_callstate_changed_recently(void) { return mdy_callstate_end_changed_id != 0; } /** Cancel call state was recently changed condition */ static void mdy_callstate_clear_changed(void) { if( mdy_callstate_end_changed_id ) { mce_log(LL_DEBUG, "suspend blocking/call state change: canceled"); g_source_remove(mdy_callstate_end_changed_id), mdy_callstate_end_changed_id = 0; // autosuspend policy mdy_stm_schedule_rethink(); } } /** Start call state was recently changed condition * * How long the condition is kept depends on the call_state. */ static void mdy_callstate_set_changed(void) { int delay = CALLSTATE_CHANGE_BLOCK_SUSPEND_DEFAULT_MS; if( call_state == CALL_STATE_ACTIVE ) delay = CALLSTATE_CHANGE_BLOCK_SUSPEND_ACTIVE_MS; if( mdy_callstate_end_changed_id ) g_source_remove(mdy_callstate_end_changed_id); else mce_log(LL_DEBUG, "suspend blocking/call state change: started"); mdy_callstate_end_changed_id = g_timeout_add(delay, mdy_callstate_end_changed_cb, 0); // autosuspend policy mdy_stm_schedule_rethink(); } /* ========================================================================= * * AUTOSUSPEND_POLICY * ========================================================================= */ #ifdef ENABLE_WAKELOCKS enum { SUSPEND_LEVEL_ON, // suspend not allowed SUSPEND_LEVEL_EARLY, // early suspend allowed SUSPEND_LEVEL_LATE, // early and late suspend allowed }; /** Automatic suspend policy */ static gint mdy_suspend_policy = MCE_DEFAULT_USE_AUTOSUSPEND; static guint mdy_suspend_policy_setting_id = 0; /** Check if suspend policy allows suspending * * @return 0 - suspending not allowed * 1 - early suspend allowed * 2 - late suspend allowed */ static int mdy_autosuspend_get_allowed_level(void) { bool block_late = false; bool block_early = false; /* no late suspend when incoming / active call */ switch( call_state ) { case CALL_STATE_RINGING: block_late = true; break; default: case CALL_STATE_ACTIVE: break; } /* no late suspend immediately after call_state change */ if( mdy_callstate_changed_recently() ) block_late = true; /* no late suspend when alarm on screen */ switch( alarm_ui_state ) { case MCE_ALARM_UI_RINGING_INT32: case MCE_ALARM_UI_VISIBLE_INT32: block_late = true; break; default: break; } /* Exceptional situations without separate state * management block late suspend */ if( uiexception_type & (UIEXCEPTION_TYPE_NOTIF|UIEXCEPTION_TYPE_LINGER) ) block_late = true; /* no late suspend in ACTDEAD etc */ if( system_state != MCE_SYSTEM_STATE_USER ) block_late = true; /* no late suspend during bootup */ if( mdy_desktop_ready_id || mdy_init_done != TRISTATE_TRUE ) block_late = true; /* no late suspend during shutdown */ if( mdy_shutdown_in_progress() ) block_late = true; /* no late suspend while PackageKit is in Locked state */ if( packagekit_locked ) block_late = true; /* no more suspend at module unload */ if( mdy_unloading_module ) block_early = true; /* no suspend during update mode */ if( mdy_osupdate_running ) block_early = true; /* do not suspend while ui side might still be drawing */ if( !mdy_compositor_is_disabled() ) block_early = true; /* adjust based on setting */ switch( mdy_suspend_policy ) { case SUSPEND_POLICY_DISABLED: block_early = true; break; case SUSPEND_POLICY_EARLY_ONLY: block_late = true; break; case SUSPEND_POLICY_DISABLE_ON_CHARGER: if( system_state == MCE_SYSTEM_STATE_USER && charger_state == CHARGER_STATE_ON ) block_early = true; break; default: case SUSPEND_POLICY_ENABLED: break; } if( block_early ) return SUSPEND_LEVEL_ON; if( block_late ) return SUSPEND_LEVEL_EARLY; return SUSPEND_LEVEL_LATE; } /** Callback for handling changes to autosuspend policy configuration * * @param client (not used) * @param id (not used) * @param entry GConf entry that changed * @param data (not used) */ static void mdy_autosuspend_setting_cb(GConfClient *const client, const guint id, GConfEntry *const entry, gpointer const data) { (void)client; (void)id; (void)data; gint policy = SUSPEND_POLICY_ENABLED; const GConfValue *value = 0; if( entry && (value = gconf_entry_get_value(entry)) ) { if( value->type == GCONF_VALUE_INT ) policy = gconf_value_get_int(value); } if( mdy_suspend_policy != policy ) { mce_log(LL_NOTICE, "suspend policy change: %d -> %d", mdy_suspend_policy, policy); mdy_suspend_policy = policy; mdy_stm_schedule_rethink(); } } #endif /* ENABLE_WAKELOCKS */ /* ========================================================================= * * ORIENTATION_ACTIVITY * ========================================================================= */ /** Callback for handling orientation change notifications * * @param state orientation sensor state */ static void mdy_orientation_changed_cb(int state) { datapipe_exec_full(&orientation_sensor_actual_pipe, GINT_TO_POINTER(state)); } /** Generate user activity from orientation sensor input */ static void mdy_orientation_generate_activity(void) { /* The feature must be enabled */ if( !mdy_orientation_change_is_activity ) goto EXIT; /* Display must be on/dimmed */ switch( display_state_curr ) { case MCE_DISPLAY_ON: case MCE_DISPLAY_DIM: break; default: goto EXIT; } mce_log(LL_DEBUG, "orientation change; generate activity"); mce_datapipe_generate_activity(); EXIT: return; } /** Check if orientation sensor should be enabled */ static bool mdy_orientation_sensor_wanted(void) { bool wanted = false; /* Skip if master toggle is disabled */ if( !mdy_orientation_sensor_enabled ) goto EXIT; /* Enable orientation sensor in ON|DIM */ /* Start the orientation sensor already when powering up * to ON|DIM|LPM_ON states -> we should have a valid sensor * state before the display transition finishes. */ switch( display_state_next ) { case MCE_DISPLAY_DIM: case MCE_DISPLAY_ON: case MCE_DISPLAY_LPM_ON: case MCE_DISPLAY_POWER_UP: break; default: goto EXIT; } /* But only if orientation sensor features are enabled */ wanted = (mdy_flipover_gesture_enabled || mdy_orientation_change_is_activity); EXIT: return wanted; } /** Start/stop orientation sensor based on display state */ static void mdy_orientation_sensor_rethink(void) { static bool was_enabled = false; bool enable = mdy_orientation_sensor_wanted(); if( was_enabled == enable ) goto EXIT; was_enabled = enable; mce_log(LL_DEBUG, "%s orientation sensor", enable ? "enable" : "disable"); if( enable ) { /* Add notification before enabling to get initial guess */ mce_sensorfw_orient_set_notify(mdy_orientation_changed_cb); mce_sensorfw_orient_enable(); } else { /* Remove notification after disabling to get final state */ mce_sensorfw_orient_disable(); mce_sensorfw_orient_set_notify(0); } EXIT: return; } /* ========================================================================= * * DISPLAY_STATE * ========================================================================= */ /* React to new display state (via display state datapipe) */ static void mdy_display_state_changed(void) { /* Program dim/blank timers */ mdy_blanking_rethink_timers(false); /* Enable/disable high brightness mode */ mdy_hbm_rethink(); /* Restart brightness fading in case automatic brightness tuning has * changed the target levels during the display state transition. * Should turn in to big nop if there are no changes. */ switch( display_state_curr ) { case MCE_DISPLAY_OFF: case MCE_DISPLAY_LPM_OFF: /* Blanking or already blanked -> set zero brightness */ mdy_brightness_force_level(0); break; case MCE_DISPLAY_LPM_ON: mdy_brightness_set_fade_target_default(mdy_brightness_level_display_lpm); break; case MCE_DISPLAY_DIM: mdy_brightness_set_fade_target_dimming(mdy_brightness_level_display_dim); break; case MCE_DISPLAY_ON: mdy_brightness_set_fade_target_default(mdy_brightness_level_display_on); break; case MCE_DISPLAY_UNDEF: break; default: case MCE_DISPLAY_POWER_DOWN: case MCE_DISPLAY_POWER_UP: // these should never show up here mce_abort(); break; } /* This will send the correct state * since the pipe contains the new value */ mdy_dbus_send_display_status(0); } /** Handle end of display state transition * * After the state machine has finished display state * tranistion, it gets broadcast to display_state_curr_pipe * via this function. * * Actions for this will be executed in display_state_curr_pipe * output trigger mdy_display_state_changed(). * * @param prev_state previous display state * @param display_state_curr state transferred to */ static void mdy_display_state_enter(display_state_t prev_state, display_state_t next_state) { mce_log(LL_INFO, "END %s -> %s transition", display_state_repr(prev_state), display_state_repr(next_state)); /* Allow ALS brightness tuning after entering powered on state */ if( mdy_stm_display_state_needs_power(next_state) ) { mce_log(LL_DEBUG, "allow als fade"); mdy_brightness_als_fade_allowed = true; } /* Restore display_state_curr_pipe to valid value */ // FIXME: datapipe value should not be directly manipulated datapipe_set_value(&display_state_curr_pipe, GINT_TO_POINTER(next_state)); /* Run display state change triggers */ mce_log(LL_CRUCIAL, "current display state = %s", display_state_repr(next_state)); datapipe_exec_full(&display_state_curr_pipe, GINT_TO_POINTER(next_state)); /* Deal with new stable display state */ mdy_display_state_changed(); } /** Handle start of display state transition * * @param prev_state display state before transition * @param display_state_curr target state to transfer to */ static void mdy_display_state_leave(display_state_t prev_state, display_state_t next_state) { mce_log(LL_INFO, "BEG %s -> %s transition", display_state_repr(prev_state), display_state_repr(next_state)); /* Cancel display state specific timers that we do not want to * trigger while waiting for frame buffer suspend/resume. */ mdy_blanking_cancel_timers(); bool have_power = mdy_stm_display_state_needs_power(prev_state); bool need_power = mdy_stm_display_state_needs_power(next_state); /* Deny ALS brightness when heading to powered off state */ if( !need_power ) { mce_log(LL_DEBUG, "deny als fade"); mdy_brightness_als_fade_allowed = false; } /* Update display brightness that should be used the next time * the display is powered up. Start fader already here if the * display is already powered up (otherwise will be started * after fb power up at STM_WAIT_RESUME / STM_LEAVE_LOGICAL_OFF). */ switch( next_state ) { case MCE_DISPLAY_ON: mdy_brightness_level_display_resume = mdy_brightness_level_display_on; if( have_power ) mdy_brightness_set_fade_target_default(mdy_brightness_level_display_resume); break; case MCE_DISPLAY_DIM: mdy_brightness_level_display_resume = mdy_brightness_level_display_dim; if( have_power ) mdy_brightness_set_fade_target_dimming(mdy_brightness_level_display_resume); break; case MCE_DISPLAY_LPM_ON: mdy_brightness_level_display_resume = mdy_brightness_level_display_lpm; if( have_power ) mdy_brightness_set_fade_target_default(mdy_brightness_level_display_resume); break; case MCE_DISPLAY_OFF: case MCE_DISPLAY_LPM_OFF: mdy_brightness_level_display_resume = 0; mdy_brightness_set_fade_target_blank(); break; case MCE_DISPLAY_UNDEF: break; default: case MCE_DISPLAY_POWER_DOWN: case MCE_DISPLAY_POWER_UP: // these should never show up here mce_abort(); break; } /* Broadcast the final target of this transition; note that this * happens while display_state_curr_pipe still holds the previous * (non-transitional) state */ mce_log(LL_NOTICE, "target display state = %s", display_state_repr(next_state)); datapipe_exec_full(&display_state_next_pipe, GINT_TO_POINTER(next_state)); /* Invalidate display_state_curr_pipe when making transitions * that need to wait for external parties */ if( have_power != need_power ) { display_state_t state = need_power ? MCE_DISPLAY_POWER_UP : MCE_DISPLAY_POWER_DOWN; mce_log(LL_CRUCIAL, "current display state = %s", display_state_repr(state)); // FIXME: datapipe value should not be directly manipulated datapipe_set_value(&display_state_curr_pipe, GINT_TO_POINTER(state)); datapipe_exec_full(&display_state_curr_pipe, GINT_TO_POINTER(state)); } } /* ========================================================================= * * FRAMEBUFFER_SUSPEND_RESUME * ========================================================================= */ /** Framebuffer suspend/resume failure led patterns */ static void mdy_fbsusp_led_set(mdy_fbsusp_led_state_t req) { bool blanking = false; bool unblanking = false; switch( req ) { case FBDEV_LED_SUSPENDING: blanking = true; mce_log(LL_DEVEL, "start alert led pattern for: failed fb suspend"); break; case FBDEV_LED_RESUMING: unblanking = true; mce_log(LL_DEVEL, "start alert led pattern for: failed fb resume"); break; default: break; } datapipe_exec_full(blanking ? &led_pattern_activate_pipe : &led_pattern_deactivate_pipe, MCE_LED_PATTERN_DISPLAY_SUSPEND_FAILED); datapipe_exec_full(unblanking ? &led_pattern_activate_pipe : &led_pattern_deactivate_pipe, MCE_LED_PATTERN_DISPLAY_RESUME_FAILED); } /** Timer id for fbdev suspend/resume is taking too long */ static guint mdy_fbsusp_led_timer_id = 0; /** Timer callback for fbdev suspend/resume is taking too long */ static gboolean mdy_fbsusp_led_timer_cb(gpointer aptr) { mdy_fbsusp_led_state_t req = GPOINTER_TO_INT(aptr); if( !mdy_fbsusp_led_timer_id ) goto EXIT; mce_log(LL_DEBUG, "fbdev led timer triggered"); mdy_fbsusp_led_timer_id = 0; mdy_fbsusp_led_set(req); EXIT: return FALSE; } /* Cancel fbdev suspend/resume is taking too long timer */ static void mdy_fbsusp_led_cancel_timer(void) { mdy_fbsusp_led_set(FBDEV_LED_OFF); if( mdy_fbsusp_led_timer_id != 0 ) { mce_log(LL_DEBUG, "fbdev led timer cancelled"); g_source_remove(mdy_fbsusp_led_timer_id), mdy_fbsusp_led_timer_id = 0; } } /* Schedule fbdev suspend/resume is taking too long timer */ static void mdy_fbsusp_led_start_timer(mdy_fbsusp_led_state_t req) { mdy_fbsusp_led_set(FBDEV_LED_OFF); int delay = LED_DELAY_FB_SUSPEND_RESUME; if( mdy_fbsusp_led_timer_id != 0 ) g_source_remove(mdy_fbsusp_led_timer_id); mdy_fbsusp_led_timer_id = g_timeout_add(delay, mdy_fbsusp_led_timer_cb, GINT_TO_POINTER(req)); mce_log(LL_DEBUG, "fbdev led timer sheduled @ %d ms", delay); } /* ========================================================================= * * DISPLAY_STATE_MACHINE * ========================================================================= */ /** Flag for: unhandled compositor availability change */ static bool mdy_stm_compositor_availability_changed = true; /** Display state we are currently in */ static display_state_t mdy_stm_curr = MCE_DISPLAY_UNDEF; /** Display state we are currently changing to */ static display_state_t mdy_stm_next = MCE_DISPLAY_UNDEF; /** Display state that has been reqyested */ static display_state_t mdy_stm_want = MCE_DISPLAY_UNDEF; /** Display state machine state */ static stm_state_t mdy_stm_dstate = STM_UNSET; /** Display state / suspend policy wakelock held */ static bool mdy_stm_acquire_wakelockd = false; /** Display state machine state to human readable string */ static const char *mdy_stm_state_name(stm_state_t state) { const char *name = "UNKNOWN"; #define DO(tag) case STM_##tag: name = #tag; break switch( state ) { DO(UNSET); DO(RENDERER_INIT_START); DO(RENDERER_WAIT_START); DO(ENTER_POWER_ON); DO(STAY_POWER_ON); DO(LEAVE_POWER_ON); DO(RENDERER_INIT_STOP); DO(RENDERER_WAIT_STOP); DO(WAIT_FADE_TO_BLACK); DO(WAIT_FADE_TO_TARGET); DO(INIT_SUSPEND); DO(WAIT_SUSPEND); DO(ENTER_POWER_OFF); DO(STAY_POWER_OFF); DO(LEAVE_POWER_OFF); DO(INIT_RESUME); DO(WAIT_RESUME); DO(ENTER_LOGICAL_OFF); DO(STAY_LOGICAL_OFF); DO(LEAVE_LOGICAL_OFF); default: break; } #undef DO return name; } /** react to compositor availability changes */ static void mdy_datapipe_compositor_service_state_cb(gconstpointer aptr) { static service_state_t service = SERVICE_STATE_UNDEF; service_state_t prev = service; service = GPOINTER_TO_INT(aptr); if( service == prev ) goto EXIT; mce_log(LL_DEVEL, "compositor_service_state = %s -> %s", service_state_repr(prev), service_state_repr(service)); /* The private name of the owner is needed when/if * the compositor gets unresponsive and needs to * be killed and restarted */ const char *curr = 0; if( service == SERVICE_STATE_RUNNING ) curr = mce_dbus_nameowner_get(COMPOSITOR_SERVICE); mdy_compositor_set_name_owner(curr); /* If compositor drops from systembus in USER/ACTDEAD mode while * we are not shutting down, assume we are dealing with lipstick * crash / act-dead-charging stop and power cycling the frame buffer is needed to clear zombie ui off the screen. */ mdy_fbdev_rethink(); /* Mark down we have availability change to handle */ mdy_stm_set_compositor_availability_changed(true); /* a) Lipstick assumes that updates are allowed when * it starts up. Try to arrange that it is so. * * b) Without lipstick in place we must not suspend * because there is nobody to communicate the * updating is allowed * * Turning the display on at lipstick runstate change * deals with both (a) and (b) * * Exception: When mce restarts while lipstick is * running, we need to keep the existing * display state. */ if( prev != SERVICE_STATE_UNDEF ) mdy_stm_push_target_change(MCE_DISPLAY_ON); /* Compositor startup during bootup always triggers display on */ if( service == SERVICE_STATE_RUNNING && mdy_init_done == TRISTATE_FALSE ) { mce_log(LL_NOTICE, "display on due to compositor startup"); mce_datapipe_request_display_state(MCE_DISPLAY_ON); } /* PID of topmost window needs to be invalidated / queried * when compositor service state changes. */ mdy_topmost_window_set_pid(-1); if( service == SERVICE_STATE_RUNNING ) mdy_topmost_window_send_pid_query(); else mdy_topmost_window_forget_pid_query(); /* In any case, reprogram blanking timers */ mdy_blanking_rethink_timers(true); EXIT: return; } /** react to systemui availability changes */ static void mdy_datapipe_lipstick_service_state_cb(gconstpointer aptr) { service_state_t prev = lipstick_service_state; lipstick_service_state = GPOINTER_TO_INT(aptr); if( lipstick_service_state == prev ) goto EXIT; mce_log(LL_DEVEL, "lipstick_service_state = %s -> %s", service_state_repr(prev), service_state_repr(lipstick_service_state)); /* Lipstick startup during bootup always triggers display on */ if( lipstick_service_state == SERVICE_STATE_RUNNING && mdy_init_done == TRISTATE_FALSE ) { mce_log(LL_NOTICE, "display on due to lipstick startup"); mce_datapipe_request_display_state(MCE_DISPLAY_ON); } mdy_blanking_rethink_afterboot_delay(); /* In any case, reprogram blanking timers */ mdy_blanking_rethink_timers(true); EXIT: return; } /** Setter function for mdy_stm_compositor_availability_changed flag * * @param changed true for availability change is detected, or * false for availability change is handled */ static void mdy_stm_set_compositor_availability_changed(bool changed) { if( mdy_stm_compositor_availability_changed != changed ) { mce_log(LL_CRUCIAL, "compositor availability change: %s", changed ? "pending" : "handled"); mdy_stm_compositor_availability_changed = changed; } } /** Predicate for choosing between STM_STAY_POWER_ON|OFF */ static bool mdy_stm_display_state_needs_power(display_state_t state) { bool power_on = true; switch( state ) { case MCE_DISPLAY_ON: case MCE_DISPLAY_DIM: case MCE_DISPLAY_LPM_ON: break; case MCE_DISPLAY_LPM_OFF: case MCE_DISPLAY_OFF: case MCE_DISPLAY_UNDEF: power_on = false; break; default: case MCE_DISPLAY_POWER_UP: case MCE_DISPLAY_POWER_DOWN: mce_abort(); } return power_on; } /** Flag for: async fbdev power up/down is not finished */ static bool mdy_stm_fbdev_pending_set_power = false; /** Callback for display power up/down ioctl finished notification * * @param aptr Requested display power state (as void pointer) * @param reply (Unused) return value from execute callback */ static void mdy_stm_fbdev_power_done_cb(void *aptr, void *reply) { (void)reply; /* Note: This is executed in the main thread context */ bool poweron = GPOINTER_TO_INT(aptr); mdy_waitfb_data.suspended = !poweron; mce_log(LL_DEBUG, "mdy_waitfb_data.suspended = %s", mdy_waitfb_data.suspended ? "true" : "false"); mdy_stm_fbdev_pending_set_power = false; mdy_stm_schedule_rethink(); } /** Callback for executing display power up/down ioctl in worker thread * * @param aptr Requested display power state (as void pointer) * * @return Requested display power state (as void pointer) */ static void *mdy_stm_fbdev_power_exec_cb(void *aptr) { /* Note: This is executed in the worker thread context */ bool poweron = GPOINTER_TO_INT(aptr); mce_log(LL_DEBUG, "display.poweron = %s", poweron ? "true" : "false"); mce_fbdev_set_power(poweron); return aptr; } /** Execute display power up/down ioctl in worker thread * * Notification code follows the same logic as is used when tracking * /sys/power/wait_for_fb_wake|sleep files in devices that have kernel * that follows early suspend model. * * @param poweron true to turn display on, false to turn it off */ static void mdy_stm_fbdev_set_power(bool poweron) { mdy_stm_fbdev_pending_set_power = true; mce_worker_add_job(MODULE_NAME, "fbdev-ioctl", mdy_stm_fbdev_power_exec_cb, mdy_stm_fbdev_power_done_cb, GINT_TO_POINTER(poweron)); } /** Predicate for: policy allows early suspend */ static bool mdy_stm_is_early_suspend_allowed(void) { #ifdef ENABLE_WAKELOCKS bool res = (mdy_autosuspend_get_allowed_level() >= SUSPEND_LEVEL_EARLY); mce_log(LL_INFO, "res=%s", res ? "true" : "false"); return res; #else // "early suspend" in state machine transforms in to // fb power control via ioctl without ENABLE_WAKELOCKS return true; #endif } /** Predicate for: policy allows late suspend */ static bool mdy_stm_is_late_suspend_allowed(void) { #ifdef ENABLE_WAKELOCKS bool res = (mdy_autosuspend_get_allowed_level() >= SUSPEND_LEVEL_LATE); mce_log(LL_INFO, "res=%s", res ? "true" : "false"); return res; #else return false; #endif } /** Flag for: Entering/leaving early suspend / autosleep */ static bool mdy_stm_autosuspend_pending = false; /** Flag for: Early suspend / autosleep enabled */ static bool mdy_stm_autosuspend_enabled = false; /** Early suspend / autosleep control sysfs write finished * * @param aptr Requested suspend state (as void pointer) * @param reply Return value from execute callback (Unused) */ static void mdy_stm_autosuspend_done_cb(void *aptr, void *reply) { (void)reply; /* Note: This is executed in the main thread context */ bool allow = GPOINTER_TO_INT(aptr); mdy_stm_autosuspend_enabled = allow; mdy_stm_autosuspend_pending = false; mce_log(LL_DEBUG, "autosuspend = %s", allow ? "enabled" : "disabled"); if( mdy_waitfb_data.thread ) { /* Early suspend: Resume implicitly wakes up display too */ } else { /* Autosleep: Explicit display power control needed */ mdy_stm_fbdev_set_power(allow ? false : true); } mdy_stm_schedule_rethink(); } /** Execute early suspend / autosleep control sysfs write * * @param aptr Requested suspend state (as void pointer) * * @return Requested suspend state (as void pointer) */ static void *mdy_stm_autosuspend_exec_cb(void *aptr) { /* Note: This is executed in the worker thread context */ bool allow = GPOINTER_TO_INT(aptr); mce_log(LL_DEBUG, "autosuspend = %s", allow ? "enabling" : "disabling"); if( allow ) wakelock_allow_suspend(); else wakelock_block_suspend(); return aptr; } /** Enable / disable early suspend / autosleep * * In some kernels the sysfs write handler does not return control * to the caller until the whole resume sequence is finished. To avoid * blocking the whole mce mainloop, the sysfs write is queued to worker * thread for asynchronous execution. * * @param allow true to allow suspending, or false to deny suspending */ static void mdy_stm_autosuspend_set_state(bool allow) { /* The state machine logic should make it sure that overlapping * control attempts never occur - raise a flag if detected anyway */ if( mdy_stm_autosuspend_pending ) mce_log(LL_WARN, "state machine fault - autosuspend control"); mdy_stm_autosuspend_pending = true; mce_worker_add_job(MODULE_NAME, "autosuspend-ctrl", mdy_stm_autosuspend_exec_cb, mdy_stm_autosuspend_done_cb, GINT_TO_POINTER(allow)); } /** Start frame buffer suspend */ static void mdy_stm_start_fb_suspend(void) { mdy_fbsusp_led_start_timer(FBDEV_LED_SUSPENDING); #ifdef ENABLE_WAKELOCKS mce_log(LL_NOTICE, "suspending"); mdy_stm_autosuspend_set_state(true); #else mce_log(LL_NOTICE, "power off frame buffer"); mdy_waitfb_data.suspended = true, mce_fbdev_set_power(false); #endif } /** Start frame buffer resume */ static void mdy_stm_start_fb_resume(void) { mdy_fbsusp_led_start_timer(FBDEV_LED_RESUMING); #ifdef ENABLE_WAKELOCKS mce_log(LL_NOTICE, "resuming"); mdy_stm_autosuspend_set_state(false); #else mce_log(LL_NOTICE, "power on frame buffer"); mdy_waitfb_data.suspended = false, mce_fbdev_set_power(true); #endif } /** Predicate for: frame buffer is powered off */ static bool mdy_stm_is_fb_suspend_finished(void) { bool res = mdy_waitfb_data.suspended; if( res ) mdy_fbsusp_led_cancel_timer(); mce_log(LL_INFO, "res=%s", res ? "true" : "false"); return res; } /** Predicate for: frame buffer is powered on */ static bool mdy_stm_is_fb_resume_finished(void) { bool res = !mdy_waitfb_data.suspended; if( res ) mdy_fbsusp_led_cancel_timer(); mce_log(LL_INFO, "res=%s", res ? "true" : "false"); return res; } /** Release display wakelock to allow late suspend */ static void mdy_stm_release_wakelock(void) { if( mdy_stm_acquire_wakelockd ) { mdy_stm_acquire_wakelockd = false; #ifdef ENABLE_WAKELOCKS mce_log(LL_INFO, "wakelock released"); wakelock_lock("mce_display_on", MCE_DISPLAY_STM_SUSPEND_DELAY_NS); #endif } } /** Acquire display wakelock to block late suspend */ static void mdy_stm_acquire_wakelock(void) { if( !mdy_stm_acquire_wakelockd ) { mdy_stm_acquire_wakelockd = true; #ifdef ENABLE_WAKELOCKS wakelock_lock("mce_display_on", -1); mce_log(LL_INFO, "wakelock acquired"); #endif } } /** Helper for making state transitions */ static void mdy_stm_trans(stm_state_t state) { if( mdy_stm_dstate != state ) { mce_log(LL_INFO, "STM: %s -> %s", mdy_stm_state_name(mdy_stm_dstate), mdy_stm_state_name(state)); mdy_stm_dstate = state; } } /** Push new change from pipeline to state machine */ static void mdy_stm_push_target_change(display_state_t next_state) { if( mdy_stm_want != next_state ) { mdy_stm_want = next_state; /* Try to initiate state transitions immediately * to make the in-transition transient states * visible to code that polls the display state * instead of using output triggers */ mdy_stm_force_rethink(); } } /** Pull new change from within the state machine */ static bool mdy_stm_pull_target_change(void) { // already in transition? if( mdy_stm_curr != mdy_stm_next ) return true; // new transition requested? if( mdy_stm_want == MCE_DISPLAY_UNDEF ) return false; mdy_stm_next = mdy_stm_want, mdy_stm_want = MCE_DISPLAY_UNDEF; // transition to new state requested? if( mdy_stm_curr == mdy_stm_next ) { /* No-change requests will be ignored, but we still * need to check if forced display state indication * signal needs to be sent. */ mdy_dbus_send_display_status(0); return false; } // do pre-transition actions mdy_display_state_leave(mdy_stm_curr, mdy_stm_next); return true; } /** Finish current change from within the state machine */ static void mdy_stm_finish_target_change(void) { // do post-transition actions display_state_t prev = mdy_stm_curr; mdy_stm_curr = mdy_stm_next; mdy_display_state_enter(prev, mdy_stm_curr); } /** Execute one state machine step * * The state transition flow implemented by this function is * described in graphviz dot language in display.dot. * * Any changes to state transition logic should be made to * display.dot too. * * As an example: generate PNG image by executing * dot -Tpng display.dot -o display.png */ static void mdy_stm_step(void) { static bool force_powerup_in_logical_off = false; switch( mdy_stm_dstate ) { default: case STM_UNSET: mdy_stm_acquire_wakelock(); if( !mdy_stm_pull_target_change() ) break; if( mdy_stm_display_state_needs_power(mdy_stm_next) ) mdy_stm_trans(STM_RENDERER_INIT_START); else mdy_stm_trans(STM_RENDERER_INIT_STOP); break; case STM_RENDERER_INIT_START: if( mdy_stm_compositor_availability_changed ) { /* If previous compositor instance exiting leaves the system * in a state where effective brightness gets set to zero, * any frames drawn by freshly started compositor can end up * getting ignored. * * In some devices kernel can: Implicitly change effective * brightness without reflecting the change on sysfs *and* * ignore sysfs changes that do not change the value as it * is available on sysfs -> To get (non-zero) effective * brightness back in sync: Do a off-by-one write first, * followed by writing the value we actually want to use. */ int brightness = mdy_brightness_level_cached; mce_log(LL_WARN, "forced brightness sync to: %d", brightness); mdy_brightness_forget_level(); if( brightness > 0 ) mdy_brightness_set_level(brightness - 1); mdy_brightness_set_level(brightness); } if( !mdy_compositor_is_available() ) { mdy_brightness_set_fade_target_unblank(mdy_brightness_level_display_resume); mdy_stm_trans(STM_WAIT_FADE_TO_TARGET); } else { mdy_compositor_enable(); mdy_stm_trans(STM_RENDERER_WAIT_START); } mdy_stm_set_compositor_availability_changed(false); break; case STM_RENDERER_WAIT_START: if( mdy_compositor_is_pending() ) break; if( mdy_compositor_is_enabled() ) { /* Case DRI compositor: Brightness adjustments made prior to * compositor finishing with setUpdatesEnabled(true) handling * might get ignored -> invalidate active brightness value so * that fade-to-target always does at least one more adjustment. */ mdy_brightness_forget_level(); mdy_brightness_set_fade_target_unblank(mdy_brightness_level_display_resume); mdy_stm_trans(STM_WAIT_FADE_TO_TARGET); break; } /* If compositor is not responsive, we must keep trying * until we get a reply - or compositor dies and drops * from system bus */ mce_log(LL_CRIT, "ui start failed, retrying"); mdy_stm_trans(STM_RENDERER_INIT_START); break; case STM_WAIT_FADE_TO_TARGET: /* If the display is already powered up and normal ui is * visible, the transition must not be blocked by ongoing * brightness fade. Otherwise the user input processing * would get misinterpreted. */ if( mdy_stm_curr == MCE_DISPLAY_ON || mdy_stm_curr == MCE_DISPLAY_DIM ) { if( mdy_brightness_level_active == mdy_brightness_level_cached ) { mdy_stm_trans(STM_ENTER_POWER_ON); break; } } /* When using sw fader, we need to wait until it is finished. * Otherwise the avalanche of activity resulting from the * display=on signal will starve mce of cpu and the brightness * transition gets really jumpy. */ if( mdy_brightness_fade_is_active() ) break; mdy_stm_trans(STM_ENTER_POWER_ON); break; case STM_ENTER_POWER_ON: /* To avoid overlapping autosuspend control attempts, we need * to stay in transient state until pending exit from early * suspend / autosleep has been finished */ if( mdy_stm_autosuspend_pending ) break; mdy_stm_finish_target_change(); mdy_stm_trans(STM_STAY_POWER_ON); break; case STM_STAY_POWER_ON: if( mdy_stm_compositor_availability_changed ) { if( !mdy_compositor_is_available() ) { /* The compositor process might have powered down * the display while making exit -> we need to * invalidate active backlight brightness level * and possibly power up the display again */ mdy_brightness_forget_level(); } mdy_stm_trans(STM_LEAVE_POWER_ON); break; } if( mdy_stm_pull_target_change() ) mdy_stm_trans(STM_LEAVE_POWER_ON); break; case STM_LEAVE_POWER_ON: if( !mdy_stm_display_state_needs_power(mdy_stm_next) ) mdy_stm_trans(STM_WAIT_FADE_TO_BLACK); else if( mdy_brightness_level_active < 0 ) mdy_stm_trans(STM_INIT_RESUME); else mdy_stm_trans(STM_RENDERER_INIT_START); break; case STM_WAIT_FADE_TO_BLACK: if( mdy_brightness_fade_is_active() ) break; /* Broadcast display off state. Any changes that get triggered on * UI side should be invisible as the backlight is already off. */ mdy_dbus_send_display_status(0); mdy_stm_trans(STM_RENDERER_INIT_STOP); break; case STM_RENDERER_INIT_STOP: if( !mdy_compositor_is_available() ) { mce_log(LL_WARN, "no compositor; going to logical off"); mdy_stm_trans(STM_ENTER_LOGICAL_OFF); } else { mdy_compositor_disable(); mdy_stm_trans(STM_RENDERER_WAIT_STOP); } mdy_stm_set_compositor_availability_changed(false); break; case STM_RENDERER_WAIT_STOP: if( mdy_compositor_is_pending() ) break; if( mdy_compositor_is_disabled() ) { /* Unless we're running on an early-suspend kernel we * have no way of knowing whether compositor has already * powered off the display. Normally it does not matter, * but must be taken into account if heading to logical * display off state. */ force_powerup_in_logical_off = !mdy_waitfb_data.thread; mdy_stm_trans(STM_INIT_SUSPEND); break; } /* If compositor is not responsive, we must keep trying * until we get a reply - or compositor dies and drops * from system bus */ mce_log(LL_CRIT, "ui stop failed, retrying"); mdy_stm_trans(STM_RENDERER_INIT_STOP); break; case STM_INIT_SUSPEND: if( mdy_stm_is_early_suspend_allowed() ) { mdy_stm_start_fb_suspend(); mdy_stm_trans(STM_WAIT_SUSPEND); } else { mdy_stm_trans(STM_ENTER_LOGICAL_OFF); } break; case STM_WAIT_SUSPEND: /* Done with async entry to early suspend / autosleep? */ if( mdy_stm_autosuspend_pending ) break; /* Done with async frame buffer device blank ioctl? */ if( mdy_stm_fbdev_pending_set_power ) break; /* Received frame buffer sleep notification? */ if( !mdy_stm_is_fb_suspend_finished() ) break; mdy_stm_trans(STM_ENTER_POWER_OFF); break; case STM_ENTER_POWER_OFF: mdy_stm_finish_target_change(); mdy_stm_trans(STM_STAY_POWER_OFF); break; case STM_STAY_POWER_OFF: if( mdy_stm_pull_target_change() ) { mdy_stm_trans(STM_LEAVE_POWER_OFF); break; } if( !mdy_stm_is_early_suspend_allowed() ) { mdy_stm_trans(STM_LEAVE_POWER_OFF); break; } /* FIXME: Need separate states for stopping/starting * sensors during suspend/resume */ if( mdy_stm_is_late_suspend_allowed() ) { mce_sensorfw_suspend(); mdy_stm_release_wakelock(); } else { mdy_stm_acquire_wakelock(); mce_sensorfw_resume(); } break; case STM_LEAVE_POWER_OFF: mdy_stm_acquire_wakelock(); mce_sensorfw_resume(); if( mdy_stm_display_state_needs_power(mdy_stm_next) ) mdy_stm_trans(STM_INIT_RESUME); else if( !mdy_stm_is_early_suspend_allowed() ) mdy_stm_trans(STM_INIT_RESUME); else mdy_stm_trans(STM_ENTER_POWER_OFF); break; case STM_INIT_RESUME: mdy_stm_start_fb_resume(); mdy_stm_trans(STM_WAIT_RESUME); break; case STM_WAIT_RESUME: /* Done with async frame buffer device unblank ioctl? */ if( mdy_stm_fbdev_pending_set_power ) break; /* Received frame buffer wakeup notification? */ if( !mdy_stm_is_fb_resume_finished() ) break; /* Note: All control branches started from here must wait * for mdy_stm_autosuspend_pending == false before * accepting new target display states. */ if( mdy_stm_display_state_needs_power(mdy_stm_next) ) { /* We must have non-zero brightness in place when ui draws * for the 1st time or the brightness changes will not happen * until ui draws again ... */ if( mdy_brightness_level_active <= 0 ) { int level = mdy_brightness_level_cached; mdy_brightness_force_level(level < 1 ? 1 : level); } mdy_stm_trans(STM_RENDERER_INIT_START); } else mdy_stm_trans(STM_ENTER_LOGICAL_OFF); break; case STM_ENTER_LOGICAL_OFF: /* To avoid overlapping autosuspend control attempts, we need * to stay in transient state until pending exit from early * suspend / autosleep has been finished */ if( mdy_stm_autosuspend_pending ) break; mdy_stm_finish_target_change(); if( force_powerup_in_logical_off ) { force_powerup_in_logical_off = false; mdy_stm_fbdev_set_power(true); } mdy_stm_trans(STM_STAY_LOGICAL_OFF); break; case STM_STAY_LOGICAL_OFF: if( mdy_stm_fbdev_pending_set_power ) break; if( mdy_stm_compositor_availability_changed ) { if( !mdy_compositor_is_available() ) mdy_brightness_forget_level(); mdy_stm_trans(STM_LEAVE_LOGICAL_OFF); break; } if( mdy_stm_pull_target_change() ) { mdy_stm_trans(STM_LEAVE_LOGICAL_OFF); break; } if( mdy_compositor_is_available() && mdy_stm_is_early_suspend_allowed() ) { mdy_stm_trans(STM_LEAVE_LOGICAL_OFF); break; } break; case STM_LEAVE_LOGICAL_OFF: force_powerup_in_logical_off = false; if( mdy_stm_pull_target_change() && mdy_stm_display_state_needs_power(mdy_stm_next) ) { if( mdy_brightness_level_active < 0 ) mdy_stm_trans(STM_INIT_RESUME); else mdy_stm_trans(STM_RENDERER_INIT_START); break; } if( mdy_stm_compositor_availability_changed ) { force_powerup_in_logical_off = !mdy_waitfb_data.thread; mdy_stm_trans(STM_RENDERER_INIT_STOP); break; } mdy_stm_trans(STM_INIT_SUSPEND); break; } } /** Simple re-entrancy counter for mdy_stm_exec() */ static int mdy_stm_in_exec = 0; /** Execute state machine steps until wait state is hit */ static void mdy_stm_exec(void) { if( ++mdy_stm_in_exec != 1 ) goto EXIT; stm_state_t prev; mce_log(LL_INFO, "ENTER @ %s", mdy_stm_state_name(mdy_stm_dstate)); do { prev = mdy_stm_dstate; mdy_stm_step(); } while( mdy_stm_dstate != prev ); mce_log(LL_INFO, "LEAVE @ %s", mdy_stm_state_name(mdy_stm_dstate)); EXIT: --mdy_stm_in_exec; } /** Timer id for state machine execution */ static guint mdy_stm_rethink_id = 0; /** Timer callback for state machine execution */ static gboolean mdy_stm_rethink_cb(gpointer aptr) { (void)aptr; // not used if( mdy_stm_rethink_id ) { /* clear pending rethink */ mdy_stm_rethink_id = 0; /* run the state machine */ mdy_stm_exec(); /* remove wakelock if not re-scheduled */ #ifdef ENABLE_WAKELOCKS if( !mdy_stm_rethink_id ) wakelock_unlock("mce_display_stm"); #endif } return FALSE; } /** Cancel state machine execution timer */ static void mdy_stm_cancel_rethink(void) { if( mdy_stm_rethink_id ) { g_source_remove(mdy_stm_rethink_id), mdy_stm_rethink_id = 0; mce_log(LL_INFO, "cancelled"); #ifdef ENABLE_WAKELOCKS wakelock_unlock("mce_display_stm"); #endif } } /** Schedule state machine execution timer */ static void mdy_stm_schedule_rethink(void) { if( !mdy_stm_rethink_id ) { #ifdef ENABLE_WAKELOCKS wakelock_lock("mce_display_stm", -1); #endif mce_log(LL_INFO, "scheduled"); mdy_stm_rethink_id = g_idle_add(mdy_stm_rethink_cb, 0); } } /** Force immediate state machine execution */ static void mdy_stm_force_rethink(void) { /* Datapipe actions from within mdy_stm_exec() can * cause feedback in the form of additional display * state requests. These are expected and ok as long * as they do not cause re-entry to mdy_stm_exec(). */ if( mdy_stm_in_exec ) goto EXIT; #ifdef ENABLE_WAKELOCKS if( !mdy_stm_rethink_id ) wakelock_lock("mce_display_stm", -1); #endif if( mdy_stm_rethink_id ) g_source_remove(mdy_stm_rethink_id), mdy_stm_rethink_id = 0; mdy_stm_exec(); #ifdef ENABLE_WAKELOCKS if( !mdy_stm_rethink_id ) wakelock_unlock("mce_display_stm"); #endif EXIT: return; } /* ========================================================================= * * DISPLAY_STATE_STATISTICS * ========================================================================= */ /** Statistics: Time spent in each display state */ static struct { int64_t entries; int64_t time_ms; } mdy_statistics_data[MCE_DISPLAY_NUMSTATES] = { [MCE_DISPLAY_UNDEF] = { 0, 0 }, [MCE_DISPLAY_OFF] = { 0, 0 }, [MCE_DISPLAY_LPM_OFF] = { 0, 0 }, [MCE_DISPLAY_LPM_ON] = { 0, 0 }, [MCE_DISPLAY_DIM] = { 0, 0 }, [MCE_DISPLAY_ON] = { 0, 0 }, [MCE_DISPLAY_POWER_UP] = { 0, 0 }, [MCE_DISPLAY_POWER_DOWN] = { 0, 0 }, }; /** Update display state statistics */ static void mdy_statistics_update(void) { /* Uptime until mce display plugin determines initial * display state gets accounted as UNDEF */ static display_state_t prev_state = MCE_DISPLAY_UNDEF; static int64_t prev_update = 0; int64_t now = mce_lib_get_boot_tick(); mdy_statistics_data[prev_state].time_ms += (now - prev_update); if( prev_state != display_state_curr ) mdy_statistics_data[display_state_curr].entries += 1; prev_state = display_state_curr; prev_update = now; } /* ========================================================================= * * CPU_SCALING_GOVERNOR * ========================================================================= */ #ifdef ENABLE_CPU_GOVERNOR /** CPU scaling governor override; not enabled by default */ static gint mdy_governor_conf = MCE_DEFAULT_CPU_SCALING_GOVERNOR; static guint mdy_governor_conf_setting_id = 0; /** GOVERNOR_DEFAULT CPU scaling governor settings */ static governor_setting_t *mdy_governor_default = 0; /** GOVERNOR_INTERACTIVE CPU scaling governor settings */ static governor_setting_t *mdy_governor_interactive = 0; /** Limit number of files that can be modified via settings */ #define GOVERNOR_MAX_SETTINGS 32 /** Obtain arrays of settings from mce ini-files * * Use mdy_governor_free_settings() to release data returned from this * function. * * If CPU scaling governor is not defined in mce INI-files, an * empty (=no-op) array of settings is returned. * * @param tag Name of CPU scaling governor state * * @return array of settings */ static governor_setting_t *mdy_governor_get_settings(const char *tag) { governor_setting_t *res = 0; size_t have = 0; size_t used = 0; char sec[128], key[128], *path, *data; snprintf(sec, sizeof sec, "CPUScalingGovernor%s", tag); if( !mce_conf_has_group(sec) ) { mce_log(LL_INFO, "Not configured: %s", sec); goto EXIT; } for( size_t i = 0; ; ++i ) { snprintf(key, sizeof key, "path%zd", i+1); path = mce_conf_get_string(sec, key, 0); if( !path || !*path ) break; if( i >= GOVERNOR_MAX_SETTINGS ) { mce_log(LL_WARN, "rejecting excess settings;" " starting from: [%s] %s", sec, key); break; } snprintf(key, sizeof key, "data%zd", i+1); data = mce_conf_get_string(sec, key, 0); if( !data ) break; if( used == have ) { have += 8; res = realloc(res, have * sizeof *res); } res[used].path = strdup(path); res[used].data = strdup(data); ++used; mce_log(LL_DEBUG, "%s[%zd]: echo > %s %s", sec, used, path, data); } if( used == 0 ) { mce_log(LL_WARN, "No items defined for: %s", sec); } EXIT: have = used + 1; res = realloc(res, have * sizeof *res); res[used].path = 0; res[used].data = 0; return res; } /** Release settings array obtained with mdy_governor_get_settings() * * @param settings array of settings, or NULL */ static void mdy_governor_free_settings(governor_setting_t *settings) { if( settings ) { for( size_t i = 0; settings[i].path; ++i ) { free(settings[i].path); free(settings[i].data); } free(settings); } } /** Write string to an already existing sysfs file * * Since the path originates from configuration data we make * some checking in order not to write to an obviously bogus * destination, namely: * 1) the path must start with /sys/devices/system/cpu/ * 2) the opened file must have the same device id as /sys * * @param path file to write to * @param data text to write * * @returns true on success, false on failure */ static bool mdy_governor_write_data(const char *path, const char *data) { static const char subtree[] = "/sys/devices/system/cpu/"; bool res = false; int todo = strlen(data); int done = 0; int fd = -1; char *dest = 0; struct stat st_sys, st_dest; /* get canonicalised absolute path */ if( !(dest = realpath(path, 0)) ) { mce_log(LL_WARN, "%s: failed to resolve real path: %m", path); goto cleanup; } /* check that the destination has more or less expected path */ if( strncmp(dest, subtree, sizeof subtree - 1) ) { mce_log(LL_WARN, "%s: not under %s", dest, subtree); goto cleanup; } /* NB: no O_CREAT & co, the file must already exist */ if( (fd = TEMP_FAILURE_RETRY(open(dest, O_WRONLY))) == -1 ) { mce_log(LL_WARN, "%s: failed to open for writing: %m", dest); goto cleanup; } /* check that the file we managed to open actually resides in sysfs */ if( stat("/sys", &st_sys) == -1 ) { mce_log(LL_WARN, "%s: failed to stat: %m", "/sys"); goto cleanup; } if( fstat(fd, &st_dest) == -1 ) { mce_log(LL_WARN, "%s: failed to stat: %m", dest); goto cleanup; } if( st_sys.st_dev != st_dest.st_dev ) { mce_log(LL_WARN, "%s: not in sysfs", dest); goto cleanup; } /* write the content */ errno = 0, done = TEMP_FAILURE_RETRY(write(fd, data, todo)); if( done != todo ) { mce_log(LL_WARN, "%s: wrote %d of %d bytes: %m", dest, done, todo); goto cleanup; } res = true; cleanup: if( fd != -1 ) TEMP_FAILURE_RETRY(close(fd)); free(dest); return res; } /** Write cpu scaling governor parameter to sysfs * * @param setting Content and where to write it */ static void mdy_governor_apply_setting(const governor_setting_t *setting) { glob_t gb; memset(&gb, 0, sizeof gb); switch( glob(setting->path, 0, 0, &gb) ) { case 0: // success break; case GLOB_NOMATCH: mce_log(LL_WARN, "%s: no matches found", setting->path); goto cleanup; case GLOB_NOSPACE: case GLOB_ABORTED: default: mce_log(LL_ERR, "%s: glob() failed", setting->path); goto cleanup; } for( size_t i = 0; i < gb.gl_pathc; ++i ) { if( mdy_governor_write_data(gb.gl_pathv[i], setting->data) ) { mce_log(LL_DEBUG, "wrote \"%s\" to: %s", setting->data, gb.gl_pathv[i]); } } cleanup: globfree(&gb); } /** Switch cpu scaling governor state * * @param state GOVERNOR_DEFAULT, GOVERNOR_DEFAULT, ... */ static void mdy_governor_set_state(int state) { const governor_setting_t *settings = 0; switch( state ) { case GOVERNOR_DEFAULT: settings = mdy_governor_default; break; case GOVERNOR_INTERACTIVE: settings = mdy_governor_interactive; break; default: break; } if( !settings ) { mce_log(LL_WARN, "governor state=%d has no mapping", state); } else { for( ; settings->path; ++settings ) { mdy_governor_apply_setting(settings); } } } /** Evaluate and apply CPU scaling governor policy */ static void mdy_governor_rethink(void) { static int governor_have = GOVERNOR_UNSET; /* By default we want to use "interactive" * cpu scaling governor, except ... */ int governor_want = GOVERNOR_INTERACTIVE; /* Use default when in transitional states */ if( system_state != MCE_SYSTEM_STATE_USER && system_state != MCE_SYSTEM_STATE_ACTDEAD ) { governor_want = GOVERNOR_DEFAULT; } /* Use default during bootup */ if( mdy_desktop_ready_id || mdy_init_done != TRISTATE_TRUE ) { governor_want = GOVERNOR_DEFAULT; } /* Use default during shutdown */ if( mdy_shutdown_in_progress() ) { governor_want = GOVERNOR_DEFAULT; } /* Restore default on unload / mce exit */ if( mdy_unloading_module ) { governor_want = GOVERNOR_DEFAULT; } /* Config override has been set */ if( mdy_governor_conf != GOVERNOR_UNSET ) { governor_want = mdy_governor_conf; } /* Apply new policy state */ if( governor_have != governor_want ) { mce_log(LL_NOTICE, "state: %d -> %d", governor_have, governor_want); mdy_governor_set_state(governor_want); governor_have = governor_want; } } /** Callback for handling changes to cpu scaling governor configuration * * @param client (not used) * @param id (not used) * @param entry GConf entry that changed * @param data (not used) */ static void mdy_governor_setting_cb(GConfClient *const client, const guint id, GConfEntry *const entry, gpointer const data) { (void)client; (void)id; (void)data; gint policy = GOVERNOR_UNSET; const GConfValue *value = 0; if( entry && (value = gconf_entry_get_value(entry)) ) { if( value->type == GCONF_VALUE_INT ) policy = gconf_value_get_int(value); } if( mdy_governor_conf != policy ) { mce_log(LL_NOTICE, "cpu scaling governor change: %d -> %d", mdy_governor_conf, policy); mdy_governor_conf = policy; mdy_governor_rethink(); } } #endif /* ENABLE_CPU_GOVERNOR */ /* ========================================================================= * * DBUS_HANDLERS * ========================================================================= */ /** Send a blanking pause status reply or signal * * @param method_call A DBusMessage to reply to; or NULL to send signal * * @return TRUE */ static gboolean mdy_dbus_send_blanking_pause_status(DBusMessage *const method_call) { static int prev = -1; bool curr = mdy_blanking_is_paused(); const char *data = (curr ? MCE_PREVENT_BLANK_ACTIVE_STRING : MCE_PREVENT_BLANK_INACTIVE_STRING); DBusMessage *msg = 0; if( method_call ) { msg = dbus_new_method_reply(method_call); mce_log(LL_DEBUG, "Sending blanking pause reply: %s", data); } else { if( prev == curr ) goto EXIT; prev = curr; msg = dbus_new_signal(MCE_SIGNAL_PATH, MCE_SIGNAL_IF, MCE_PREVENT_BLANK_SIG); mce_log(LL_DEVEL, "Sending blanking pause signal: %s", data); } if( !dbus_message_append_args(msg, DBUS_TYPE_STRING, &data, DBUS_TYPE_INVALID) ) goto EXIT; dbus_send_message(msg), msg = 0; EXIT: if( msg ) dbus_message_unref(msg); return TRUE; } /** Send a blanking pause allowed reply or signal * * @param method_call A DBusMessage to reply to; or NULL to send signal * * @return TRUE */ static gboolean mdy_dbus_send_blanking_pause_allowed_status(DBusMessage *const method_call) { static int prev = -1; bool curr = blanking_pause_allowed; dbus_bool_t data = curr; DBusMessage *msg = 0; if( method_call ) { msg = dbus_new_method_reply(method_call); mce_log(LL_DEBUG, "Sending blanking pause allowed reply: %s", data ? "true" : "false"); } else if( prev == curr ) { /* Omit no-change signals */ goto EXIT; } else { prev = curr; msg = dbus_new_signal(MCE_SIGNAL_PATH, MCE_SIGNAL_IF, MCE_PREVENT_BLANK_ALLOWED_SIG); mce_log(LL_DEVEL, "Sending blanking pause allowed signal: %s", data ? "true" : "false"); } if( !dbus_message_append_args(msg, DBUS_TYPE_BOOLEAN, &data, DBUS_TYPE_INVALID) ) goto EXIT; dbus_send_message(msg), msg = 0; EXIT: if( msg ) dbus_message_unref(msg); return TRUE; } /** D-Bus callback for the get blanking pause status method call * * @param msg The D-Bus message * * @return TRUE */ static gboolean mdy_dbus_handle_blanking_pause_get_req(DBusMessage *const msg) { mce_log(LL_DEVEL, "Received blanking pause status get request from %s", mce_dbus_get_message_sender_ident(msg)); mdy_dbus_send_blanking_pause_status(msg); return TRUE; } /** D-Bus callback for the get blanking pause allowed method call * * @param msg The D-Bus message * * @return TRUE */ static gboolean mdy_dbus_handle_blanking_pause_allowed_get_req(DBusMessage *const msg) { mce_log(LL_DEVEL, "Received blanking pause allowed get request from %s", mce_dbus_get_message_sender_ident(msg)); mdy_dbus_send_blanking_pause_allowed_status(msg); return TRUE; } /** Send a blanking inhibit status reply or signal * * @param method_call A DBusMessage to reply to; or NULL to send signal * * @return TRUE */ static gboolean mdy_dbus_send_blanking_inhibit_status(DBusMessage *const method_call) { static int prev = -1; bool curr = false; /* Having blanking pause active counts as inhibit active too */ if( mdy_blanking_is_paused() ) curr = true; /* In display on/dim state we should have either dimming or * blanking timer active. If that is not the case, some form of * blanking inhibit is active. This should catch things like * stay-on inhibit modes, update mode, never-blank mode, etc */ if( display_state_curr == MCE_DISPLAY_ON || display_state_curr == MCE_DISPLAY_DIM ) { if( !mdy_blanking_off_cb_id && !mdy_blanking_dim_cb_id ) curr = true; } /* The stay-dim inhibit modes do not prevent dimming, so those need * to be taken into account separately. */ if( display_state_curr == MCE_DISPLAY_ON && mdy_blanking_dim_cb_id ) { if( mdy_blanking_inhibit_dim_p() ) curr = true; } DBusMessage *msg = 0; const char *data = (curr ? MCE_INHIBIT_BLANK_ACTIVE_STRING : MCE_INHIBIT_BLANK_INACTIVE_STRING); if( method_call ) { msg = dbus_new_method_reply(method_call); mce_log(LL_DEBUG, "Sending blanking inhibit reply: %s", data); } else { if( prev == curr ) goto EXIT; prev = curr; msg = dbus_new_signal(MCE_SIGNAL_PATH, MCE_SIGNAL_IF, MCE_BLANKING_INHIBIT_SIG); mce_log(LL_DEVEL, "Sending blanking inhibit signal: %s", data); } if( !dbus_message_append_args(msg, DBUS_TYPE_STRING, &data, DBUS_TYPE_INVALID) ) goto EXIT; dbus_send_message(msg), msg = 0; EXIT: if( msg ) dbus_message_unref(msg); return TRUE; } /** D-Bus callback for the get blanking inhibit status method call * * @param msg The D-Bus message * * @return TRUE */ static gboolean mdy_dbus_handle_blanking_inhibit_get_req(DBusMessage *const msg) { mce_log(LL_DEVEL, "Received blanking inhibit status get request from %s", mce_dbus_get_message_sender_ident(msg)); mdy_dbus_send_blanking_inhibit_status(msg); return TRUE; } /** Latest display state indication that was broadcast over D-Bus */ static const gchar *mdy_dbus_last_display_state = ""; /** Clear lastest display state sent to force re-broadcasting */ static void mdy_dbus_invalidate_display_status(void) { mdy_dbus_last_display_state = ""; } /** * Send a display status reply or signal * * @param method_call A DBusMessage to reply to; * pass NULL to send a display status signal instead * @return TRUE on success, FALSE on failure */ static gboolean mdy_dbus_send_display_status(DBusMessage *const method_call) { gboolean status = FALSE; DBusMessage *msg = NULL; /* Signal broadcasting activity defines also what should * be returned when query via dbus method call is made. */ static const char *prev = 0; if( !method_call ) { /* Evaluate whether a signal can be send at this time. */ switch( display_state_next ) { case MCE_DISPLAY_ON: case MCE_DISPLAY_DIM: case MCE_DISPLAY_LPM_ON: /* Powered on states can be signaled only after * finishing the transition. */ if( display_state_curr != display_state_next ) goto EXIT; break; default: /* Other states *can* be signaled already during the * transition. */ break; } /* Update what state name to expose on D-Bus. */ switch( display_state_next ) { default: prev = MCE_DISPLAY_OFF_STRING; break; case MCE_DISPLAY_DIM: prev = MCE_DISPLAY_DIM_STRING; break; case MCE_DISPLAY_ON: prev = MCE_DISPLAY_ON_STRING; break; } /* Avoid sending no-change signals */ if( !g_strcmp0(mdy_dbus_last_display_state, prev)) goto EXIT; mdy_dbus_last_display_state = prev; } /* Previously sent / just updated broadcast value. Or safe * fallback value in case something somehow managed to make * a method call before initial display state broadcast. */ const char *curr = prev ?: MCE_DISPLAY_OFF_STRING; /* Signal broadcast, or method call reply */ if( !method_call ) { mce_log(LL_NOTICE, "Sending display status signal: %s", curr); msg = dbus_new_signal(MCE_SIGNAL_PATH, MCE_SIGNAL_IF, MCE_DISPLAY_SIG); } else { mce_log(LL_DEBUG, "Sending display status reply: %s", curr); msg = dbus_new_method_reply(method_call); } /* Append the display status */ if (dbus_message_append_args(msg, DBUS_TYPE_STRING, &curr, DBUS_TYPE_INVALID) == FALSE) { mce_log(LL_ERR, "Failed to append %sargument to D-Bus message " "for %s.%s", method_call ? "reply " : "", method_call ? MCE_REQUEST_IF : MCE_SIGNAL_IF, method_call ? MCE_DISPLAY_STATUS_GET : MCE_DISPLAY_SIG); dbus_message_unref(msg); goto EXIT; } /* Send the message */ status = dbus_send_message(msg); EXIT: return status; } /** Helper for deciding if external display on/dim request are allowed * * There are state machines handling display on/off during * calls and alarms. We do not want external requests to interfere * with them. * * @return reason to block, or NULL if allowed */ static const char *mdy_dbus_get_reason_to_block_display_on(void) { const char *reason = 0; /* display off? */ switch( display_state_curr ) { default: case MCE_DISPLAY_OFF: case MCE_DISPLAY_LPM_OFF: case MCE_DISPLAY_LPM_ON: case MCE_DISPLAY_POWER_UP: case MCE_DISPLAY_POWER_DOWN: case MCE_DISPLAY_UNDEF: break; case MCE_DISPLAY_DIM: case MCE_DISPLAY_ON: // it is already powered on, nothing to block goto EXIT; } /* system state must be USER or ACT DEAD */ switch( system_state ) { case MCE_SYSTEM_STATE_USER: case MCE_SYSTEM_STATE_ACTDEAD: break; default: reason = "system_state != USER|ACTDEAD"; goto EXIT; } /* active calls? */ switch( call_state ) { case CALL_STATE_RINGING: case CALL_STATE_ACTIVE: reason = "call ringing|active"; goto EXIT; default: break; } /* active alarms? */ switch( alarm_ui_state ) { case MCE_ALARM_UI_RINGING_INT32: case MCE_ALARM_UI_VISIBLE_INT32: reason = "active alarm"; goto EXIT; default: break; } /* lid closed? */ if( lid_sensor_filtered == COVER_CLOSED ) { reason = "lid closed"; goto EXIT; } /* proximity covered or unknown? */ if( proximity_sensor_actual != COVER_OPEN ) { reason = "proximity covered"; goto EXIT; } EXIT: return reason; } /** Helper for deciding if external display off requests are allowed * * During ringing call / alarm power key is reserved for silencing * the ringing -> there might not be a way to power up display again * -> can't allow it to be powered down on external trigger * * @return reason to block, or NULL if allowed */ static const char *mdy_dbus_get_reason_to_block_display_off(void) { const char *reason = NULL; /* incoming call? */ switch( call_state ) { case CALL_STATE_RINGING: reason = "call ringing"; goto EXIT; default: break; } /* active alarm? */ switch( alarm_ui_state ) { case MCE_ALARM_UI_RINGING_INT32: case MCE_ALARM_UI_VISIBLE_INT32: reason = "active alarm"; goto EXIT; default: break; } EXIT: return reason; } /** Helper for deciding if external display lpm_on requests are allowed * * Check if device in a state where lpm display can be activated * without causing problems elsewhere. * * @return reason to block, or NULL if allowed */ static const char *mdy_dbus_get_reason_to_block_display_lpm(void) { const char *reason = NULL; /* If there are any reasons to ignore display on/dim requests, * they apply also for lpm-on requests */ if( (reason = mdy_dbus_get_reason_to_block_display_on()) ) goto EXIT; /* Ignore lpm-on requests if there are active calls / alarms */ if( uiexception_type & (UIEXCEPTION_TYPE_CALL | UIEXCEPTION_TYPE_ALARM) ) { reason = "call or alarm active"; goto EXIT; } /* UI side is allowed to trigger lpm from fully powered up display * states, i.e. blank-to-lpm is allowed, but unblank-to-lpm is not */ switch( display_state_next ) { case MCE_DISPLAY_DIM: case MCE_DISPLAY_ON: break; default: reason = "display is off"; goto EXIT; } EXIT: return reason; } /** Helper for handling display state requests coming over D-Bus * * @param state Requested display state */ static void mdy_dbus_handle_display_state_req(display_state_t state) { /* When dealing with display state requests coming over D-Bus, * we need to make sure an indication signal is sent even if * the request gets ignored due to never-blank mode or something * similar -> reset the last indication sent cache */ mdy_dbus_invalidate_display_status(); mce_datapipe_request_display_state(state); } /** Delayed processing of display state request received over dbus * * @param aptr requested display state (as void pointer) */ static void mdy_dbus_handle_display_state_req_cb(gpointer aptr) { display_state_t requested = GPOINTER_TO_INT(aptr); const char *reason = NULL; bool tklock = false; bool devlock = false; if( requested == MCE_DISPLAY_OFF ) { /* Note: inequality test here vs bit tests below is intentional * as "only-blank" is meaningful only when it is the only * selection present in the bitmask. */ if( mdy_dbus_display_off_override != DISPLAY_OFF_OVERRIDE_ONLY_BLANK ) tklock = true; if( mdy_dbus_display_off_override & DISPLAY_OFF_OVERRIDE_USE_LPM ) requested = MCE_DISPLAY_LPM_ON; if( mdy_dbus_display_off_override & DISPLAY_OFF_OVERRIDE_DEVLOCK ) devlock = true; } switch( requested ) { case MCE_DISPLAY_OFF: reason = mdy_dbus_get_reason_to_block_display_off(); break; case MCE_DISPLAY_ON: case MCE_DISPLAY_DIM: reason = mdy_dbus_get_reason_to_block_display_on(); break; case MCE_DISPLAY_LPM_ON: tklock = true; if( (reason = mdy_dbus_get_reason_to_block_display_lpm()) ) { mce_log(LL_WARN, "display %s request denied: %s", display_state_repr(requested), reason); requested = MCE_DISPLAY_OFF; reason = mdy_dbus_get_reason_to_block_display_off(); } break; default: reason = "unexpected state requested"; break; } if( reason ) mce_log(LL_WARN, "display %s request denied: %s", display_state_repr(requested), reason); if( tklock ) mce_datapipe_request_tklock(TKLOCK_REQUEST_ON); if( !reason && requested != display_state_next ) mdy_dbus_handle_display_state_req(requested); if( devlock ) { dbus_int32_t state = DEVICELOCK_STATE_LOCKED; mce_log(LL_WARN, "Requesting devicelock=%s", devicelock_state_repr(state)); dbus_send(DEVICELOCK_SERVICE, DEVICELOCK_REQUEST_PATH, DEVICELOCK_REQUEST_IF, "setState", NULL, DBUS_TYPE_INT32, &state, DBUS_TYPE_INVALID); } } /** Delay processing of display state request until proximity state is known * * If proximity sensor state is already known, the action is executed * immediately. Otherwise it will be delayed until sensor ramp-up is * finished. * * Note that evaluation delay (due to on-demand proximity sensor use) * has small, but non-zero chance of introducing visual hiccups. * * @param msg DBus method call message (for diagnostic logging purposes) * @param state requested display state */ static void mdy_dbus_schedule_display_state_req(DBusMessage *const msg, display_state_t state) { mce_log(LL_CRUCIAL,"display %s request from %s", display_state_repr(state), mce_dbus_get_message_sender_ident(msg)); common_on_proximity_schedule(MODULE_NAME, mdy_dbus_handle_display_state_req_cb, GINT_TO_POINTER(state)); } /** * D-Bus callback for the display on method call * * @param msg The D-Bus message * * @return TRUE */ static gboolean mdy_dbus_handle_display_on_req(DBusMessage *const msg) { mdy_dbus_schedule_display_state_req(msg, MCE_DISPLAY_ON); if( !dbus_message_get_no_reply(msg) ) dbus_send_message(dbus_new_method_reply(msg)); return TRUE; } /** * D-Bus callback for the display dim method call * * @param msg The D-Bus message * * @return TRUE */ static gboolean mdy_dbus_handle_display_dim_req(DBusMessage *const msg) { mdy_dbus_schedule_display_state_req(msg, MCE_DISPLAY_DIM); if( !dbus_message_get_no_reply(msg) ) dbus_send_message(dbus_new_method_reply(msg)); return TRUE; } /** * D-Bus callback for the display off method call * * @param msg The D-Bus message * * @return TRUE */ static gboolean mdy_dbus_handle_display_off_req(DBusMessage *const msg) { mdy_dbus_schedule_display_state_req(msg, MCE_DISPLAY_OFF); if( !dbus_message_get_no_reply(msg) ) dbus_send_message(dbus_new_method_reply(msg)); return TRUE; } /** D-Bus callback for the display lpm method call * * @param msg The D-Bus message * * @return TRUE */ static gboolean mdy_dbus_handle_display_lpm_req(DBusMessage *const msg) { mdy_dbus_schedule_display_state_req(msg, MCE_DISPLAY_LPM_ON); if( !dbus_message_get_no_reply(msg) ) dbus_send_message(dbus_new_method_reply(msg)); return TRUE; } /** D-Bus callback for the display lpm-on method call * * @param msg The D-Bus message * * @return TRUE */ #ifdef ENABLE_DEVEL_LOGGING static gboolean mdy_dbus_handle_display_lpm_on_req(DBusMessage *const msg) { display_state_t request = MCE_DISPLAY_LPM_ON; mce_log(LL_CRUCIAL, "display lpm-on request from %s", mce_dbus_get_message_sender_ident(msg)); if( !dbus_message_get_no_reply(msg) ) dbus_send_message(dbus_new_method_reply(msg)); mdy_dbus_handle_display_state_req(request); return TRUE; } #endif /** D-Bus callback for the display lpm-off method call * * @param msg The D-Bus message * * @return TRUE */ #ifdef ENABLE_DEVEL_LOGGING static gboolean mdy_dbus_handle_display_lpm_off_req(DBusMessage *const msg) { display_state_t request = MCE_DISPLAY_LPM_OFF; mce_log(LL_CRUCIAL, "display lpm-off request from %s", mce_dbus_get_message_sender_ident(msg)); if( !dbus_message_get_no_reply(msg) ) dbus_send_message(dbus_new_method_reply(msg)); mdy_dbus_handle_display_state_req(request); return TRUE; } #endif /** * D-Bus callback for the get display status method call * * @param msg The D-Bus message * @return TRUE on success, FALSE on failure */ static gboolean mdy_dbus_handle_display_status_get_req(DBusMessage *const msg) { gboolean status = FALSE; mce_log(LL_DEVEL, "Received display status get request from %s", mce_dbus_get_message_sender_ident(msg)); /* Try to send a reply that contains the current display status */ if (mdy_dbus_send_display_status(msg) == FALSE) goto EXIT; status = TRUE; EXIT: return status; } /** * Send a CABC status reply * * @param method_call A DBusMessage to reply to * @return TRUE on success, FALSE on failure */ static gboolean mdy_dbus_send_cabc_mode(DBusMessage *const method_call) { DBusMessage *msg = NULL; gboolean status = FALSE; /* Resolve what to send * * Note that possible PSM time override is ignored and only the * supposedly active CABC mode is exposed on D-Bus. */ const char *send_mode = NULL; const char *have_mode = mdy_cabc_mode_def; if( have_mode ) { for( size_t i = 0; mdy_cabc_mode_mapping[i].sysfs; ++i ) { if( !strcmp(mdy_cabc_mode_mapping[i].sysfs, have_mode) ) { send_mode = mdy_cabc_mode_mapping[i].dbus; break; } } } if( !send_mode ) send_mode = MCE_CABC_MODE_OFF; mce_log(LL_DEBUG,"Sending CABC mode: %s", send_mode); /* Construct and send reply message */ msg = dbus_new_method_reply(method_call); if( !dbus_message_append_args(msg, DBUS_TYPE_STRING, &send_mode, DBUS_TYPE_INVALID) ) { mce_log(LL_ERR, "Failed to append reply argument to D-Bus message " "for %s.%s", MCE_REQUEST_IF, MCE_CABC_MODE_GET); goto EXIT; } status = dbus_send_message(msg), msg = 0; EXIT: if( msg ) dbus_message_unref(msg); return status; } /** * D-Bus callback used for monitoring the process that requested * CABC mode change; if that process exits, immediately * restore the CABC mode to the default * * @param msg The D-Bus message * @return TRUE on success, FALSE on failure */ static gboolean mdy_dbus_handle_cabc_mode_owner_lost_sig(DBusMessage *const msg) { gboolean status = FALSE; const gchar *old_name; const gchar *new_name; const gchar *service; DBusError error = DBUS_ERROR_INIT; /* Extract result */ if (dbus_message_get_args(msg, &error, DBUS_TYPE_STRING, &service, DBUS_TYPE_STRING, &old_name, DBUS_TYPE_STRING, &new_name, DBUS_TYPE_INVALID) == FALSE) { mce_log(LL_ERR, "Failed to get argument from %s.%s; %s", "org.freedesktop.DBus", "NameOwnerChanged", error.message); goto EXIT; } /* Remove the name monitor for the CABC mode */ mce_dbus_owner_monitor_remove_all(&mdy_cabc_mode_monitor_list); mdy_cabc_mode_set(DEFAULT_CABC_MODE); status = TRUE; EXIT: dbus_error_free(&error); return status; } /** * D-Bus callback for the get CABC mode method call * * @param msg The D-Bus message * @return TRUE on success, FALSE on failure */ static gboolean mdy_dbus_handle_cabc_mode_get_req(DBusMessage *const msg) { gboolean status = FALSE; mce_log(LL_DEVEL, "Received CABC mode get request from %s", mce_dbus_get_message_sender_ident(msg)); /* Try to send a reply that contains the current CABC mode */ if (mdy_dbus_send_cabc_mode(msg) == FALSE) goto EXIT; status = TRUE; EXIT: return status; } /** * D-Bus callback for the set CABC mode method call * * @param msg The D-Bus message * @return TRUE on success, FALSE on failure */ static gboolean mdy_dbus_handle_cabc_mode_set_req(DBusMessage *const msg) { dbus_bool_t no_reply = dbus_message_get_no_reply(msg); const gchar *sender = dbus_message_get_sender(msg); const gchar *sysfs_cabc_mode = NULL; const gchar *dbus_cabc_mode = NULL; gboolean status = FALSE; gint i; DBusError error = DBUS_ERROR_INIT; if (sender == NULL) { mce_log(LL_ERR, "invalid set CABC mode request (NULL sender)"); goto EXIT; } mce_log(LL_DEVEL, "Received set CABC mode request from %s", mce_dbus_get_name_owner_ident(sender)); /* Extract result */ if (dbus_message_get_args(msg, &error, DBUS_TYPE_STRING, &dbus_cabc_mode, DBUS_TYPE_INVALID) == FALSE) { mce_log(LL_ERR, "Failed to get argument from %s.%s; %s", MCE_REQUEST_IF, MCE_CABC_MODE_REQ, error.message); goto EXIT; } for (i = 0; mdy_cabc_mode_mapping[i].dbus != NULL; i++) { if (!strcmp(mdy_cabc_mode_mapping[i].dbus, dbus_cabc_mode)) { sysfs_cabc_mode = mdy_cabc_mode_mapping[i].sysfs; } } /* Use the default if the requested mode was invalid */ if (sysfs_cabc_mode == NULL) { mce_log(LL_WARN, "Invalid CABC mode requested; using %s", DEFAULT_CABC_MODE); sysfs_cabc_mode = DEFAULT_CABC_MODE; } mdy_cabc_mode_set(sysfs_cabc_mode); /* We only ever monitor one owner; latest wins */ mce_dbus_owner_monitor_remove_all(&mdy_cabc_mode_monitor_list); if (mce_dbus_owner_monitor_add(sender, mdy_dbus_handle_cabc_mode_owner_lost_sig, &mdy_cabc_mode_monitor_list, 1) == -1) { mce_log(LL_INFO, "Failed to add name owner monitoring for `%s'", mce_dbus_get_name_owner_ident(sender)); } /* If reply is wanted, send the current CABC mode */ if (no_reply == FALSE) { DBusMessage *reply = dbus_new_method_reply(msg); for (i = 0; mdy_cabc_mode_mapping[i].sysfs != NULL; i++) { if (!strcmp(sysfs_cabc_mode, mdy_cabc_mode_mapping[i].sysfs)) { // XXX: error handling! dbus_message_append_args(reply, DBUS_TYPE_STRING, &mdy_cabc_mode_mapping[i].dbus, DBUS_TYPE_INVALID); break; } } status = dbus_send_message(reply); } else { status = TRUE; } EXIT: dbus_error_free(&error); return status; } /** * D-Bus callback for display blanking prevent request method call * * @param msg The D-Bus message * @return TRUE on success, FALSE on failure */ static gboolean mdy_dbus_handle_blanking_pause_start_req(DBusMessage *const msg) { gboolean status = FALSE; dbus_bool_t no_reply = dbus_message_get_no_reply(msg); const gchar *sender = dbus_message_get_sender(msg); if( !sender ) { mce_log(LL_ERR, "invalid blanking pause request (NULL sender)"); goto EXIT; } mce_log(LL_DEVEL, "blanking pause request from %s", mce_dbus_get_name_owner_ident(sender)); mdy_blanking_add_pause_client(sender); if( no_reply ) status = TRUE; else { DBusMessage *reply = dbus_new_method_reply(msg); status = dbus_send_message(reply), reply = 0; } EXIT: return status; } /** * D-Bus callback for display cancel blanking prevent request method call * * @param msg The D-Bus message * @return TRUE on success, FALSE on failure */ static gboolean mdy_dbus_handle_blanking_pause_cancel_req(DBusMessage *const msg) { gboolean status = FALSE; dbus_bool_t no_reply = dbus_message_get_no_reply(msg); const gchar *sender = dbus_message_get_sender(msg); if( !sender ) { mce_log(LL_ERR, "invalid cancel blanking pause request (NULL sender)"); goto EXIT; } mce_log(LL_DEVEL, "cancel blanking pause request from %s", mce_dbus_get_name_owner_ident(sender)); mdy_blanking_remove_pause_client(sender); if( no_reply) status = TRUE; else { DBusMessage *reply = dbus_new_method_reply(msg); status = dbus_send_message(reply), reply = 0; } EXIT: return status; } /** D-Bus callback for the get display statistics method call * * @param req The D-Bus method call message to be replied * * @return TRUE */ static gboolean mdy_dbus_handle_display_stats_get_req(DBusMessage *const req) { DBusMessage *rsp = 0; DBusMessageIter body; DBusMessageIter array; DBusMessageIter dict; DBusMessageIter entry; mce_log(LL_DEVEL, "display state statistics req from %s", mce_dbus_get_message_sender_ident(req)); if( dbus_message_get_no_reply(req) ) goto EXIT; rsp = dbus_new_method_reply(req); dbus_message_iter_init_append(rsp, &body); if( !dbus_message_iter_open_container(&body, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_STRUCT_BEGIN_CHAR_AS_STRING DBUS_TYPE_INT64_AS_STRING DBUS_TYPE_INT64_AS_STRING DBUS_STRUCT_END_CHAR_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &array) ) goto EXIT; /* Add data accumulated for the current display state * before constructing the reply message */ mdy_statistics_update(); for( size_t i = 0; i < MCE_DISPLAY_NUMSTATES; ++i ) { dbus_any_t dta; if( !dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, 0, &dict) ) goto ABANDON_ARRAY; dta.s = display_state_repr(i); if( !dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &dta) ) goto ABANDON_DICT; if( !dbus_message_iter_open_container(&dict, DBUS_TYPE_STRUCT, 0, &entry) ) goto ABANDON_DICT; dta.i64 = mdy_statistics_data[i].time_ms; if( !dbus_message_iter_append_basic(&entry, DBUS_TYPE_INT64, &dta) ) goto ABANDON_ENTRY; dta.i64 = mdy_statistics_data[i].entries; if( !dbus_message_iter_append_basic(&entry, DBUS_TYPE_INT64, &dta) ) goto ABANDON_ENTRY; if( !dbus_message_iter_close_container(&dict, &entry) ) goto ABANDON_DICT; if( !dbus_message_iter_close_container(&array, &dict) ) goto ABANDON_ARRAY; } if( !dbus_message_iter_close_container(&body, &array) ) goto EXIT; dbus_send_message(rsp), rsp = 0; goto EXIT; ABANDON_ENTRY: dbus_message_iter_abandon_container(&dict, &entry); ABANDON_DICT: dbus_message_iter_abandon_container(&array, &dict); ABANDON_ARRAY: dbus_message_iter_abandon_container(&body, &array); EXIT: if( rsp ) dbus_message_unref(rsp); return TRUE; } /** * D-Bus callback for the desktop startup notification signal * * @param msg The D-Bus message * @return TRUE on success, FALSE on failure */ static gboolean mdy_dbus_handle_desktop_started_sig(DBusMessage *const msg) { gboolean status = FALSE; (void)msg; mce_log(LL_NOTICE, "Received desktop startup notification"); mce_log(LL_DEBUG, "deactivate MCE_LED_PATTERN_POWER_ON"); datapipe_exec_full(&led_pattern_deactivate_pipe, MCE_LED_PATTERN_POWER_ON); mce_rem_submode_int32(MCE_SUBMODE_BOOTUP); mce_rem_submode_int32(MCE_SUBMODE_MALF); if (g_access(MCE_MALF_FILENAME, F_OK) == 0) { g_remove(MCE_MALF_FILENAME); } /* Reprogram blanking timers */ mdy_blanking_rethink_timers(true); status = TRUE; return status; } /** Array of dbus message handlers */ static mce_dbus_handler_t mdy_dbus_handlers[] = { /* signals - outbound (for Introspect purposes only) */ { .interface = MCE_SIGNAL_IF, .name = MCE_PREVENT_BLANK_SIG, .type = DBUS_MESSAGE_TYPE_SIGNAL, .args = " \n" }, { .interface = MCE_SIGNAL_IF, .name = MCE_PREVENT_BLANK_ALLOWED_SIG, .type = DBUS_MESSAGE_TYPE_SIGNAL, .args = " \n" }, { .interface = MCE_SIGNAL_IF, .name = MCE_BLANKING_INHIBIT_SIG, .type = DBUS_MESSAGE_TYPE_SIGNAL, .args = " \n" }, { .interface = MCE_SIGNAL_IF, .name = MCE_DISPLAY_SIG, .type = DBUS_MESSAGE_TYPE_SIGNAL, .args = " \n" }, { .interface = MCE_SIGNAL_IF, .name = MCE_FADER_OPACITY_SIG, .type = DBUS_MESSAGE_TYPE_SIGNAL, .args = " \n" " \n" }, /* signals */ { .interface = "com.nokia.startup.signal", .name = "desktop_visible", .type = DBUS_MESSAGE_TYPE_SIGNAL, .callback = mdy_dbus_handle_desktop_started_sig, }, { .interface = COMPOSITOR_IFACE, .name = COMPOSITOR_TOPMOST_WINDOW_PID_CHANGED, .type = DBUS_MESSAGE_TYPE_SIGNAL, .callback = mdy_topmost_window_pid_changed_cb, }, /* method calls */ { .interface = MCE_REQUEST_IF, .name = MCE_PREVENT_BLANK_GET, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = mdy_dbus_handle_blanking_pause_get_req, .args = " \n" }, { .interface = MCE_REQUEST_IF, .name = MCE_PREVENT_BLANK_ALLOWED_GET, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = mdy_dbus_handle_blanking_pause_allowed_get_req, .args = " \n" }, { .interface = MCE_REQUEST_IF, .name = MCE_BLANKING_INHIBIT_GET, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = mdy_dbus_handle_blanking_inhibit_get_req, .args = " \n" }, { .interface = MCE_REQUEST_IF, .name = MCE_DISPLAY_STATUS_GET, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = mdy_dbus_handle_display_status_get_req, .args = " \n" }, { .interface = MCE_REQUEST_IF, .name = MCE_CABC_MODE_GET, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = mdy_dbus_handle_cabc_mode_get_req, .args = " \n" }, { .interface = MCE_REQUEST_IF, .name = MCE_DISPLAY_ON_REQ, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = mdy_dbus_handle_display_on_req, .args = "" }, { .interface = MCE_REQUEST_IF, .name = MCE_DISPLAY_DIM_REQ, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = mdy_dbus_handle_display_dim_req, .args = "" }, { .interface = MCE_REQUEST_IF, .name = MCE_DISPLAY_OFF_REQ, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = mdy_dbus_handle_display_off_req, .args = "" }, { .interface = MCE_REQUEST_IF, .name = MCE_DISPLAY_LPM_REQ, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = mdy_dbus_handle_display_lpm_req, .args = "" }, #ifdef ENABLE_DEVEL_LOGGING { .interface = MCE_REQUEST_IF, .name = MCE_DISPLAY_STATE_LPM_ON_REQ, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = mdy_dbus_handle_display_lpm_on_req, .privileged = true, .args = "" }, { .interface = MCE_REQUEST_IF, .name = MCE_DISPLAY_STATE_LPM_OFF_REQ, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = mdy_dbus_handle_display_lpm_off_req, .privileged = true, .args = "" }, #endif { .interface = MCE_REQUEST_IF, .name = MCE_PREVENT_BLANK_REQ, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = mdy_dbus_handle_blanking_pause_start_req, .args = "" }, { .interface = MCE_REQUEST_IF, .name = MCE_CANCEL_PREVENT_BLANK_REQ, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = mdy_dbus_handle_blanking_pause_cancel_req, .args = "" }, { .interface = MCE_REQUEST_IF, .name = MCE_CABC_MODE_REQ, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = mdy_dbus_handle_cabc_mode_set_req, .args = " \n" " \n" }, { .interface = MCE_REQUEST_IF, .name = MCE_DISPLAY_STATS_GET, .type = DBUS_MESSAGE_TYPE_METHOD_CALL, .callback = mdy_dbus_handle_display_stats_get_req, .args = " \n" }, /* sentinel */ { .interface = 0 } }; /** Install dbus message handlers */ static void mdy_dbus_init(void) { mce_dbus_handler_register_array(mdy_dbus_handlers); } /** Remove dbus message handlers */ static void mdy_dbus_quit(void) { mce_dbus_handler_unregister_array(mdy_dbus_handlers); } /* ========================================================================= * * FLAG_FILE_TRACKING * ========================================================================= */ /** Simulated "desktop ready" via uptime based timer */ static gboolean mdy_flagfiles_desktop_ready_cb(gpointer user_data) { (void)user_data; if( mdy_desktop_ready_id ) { mdy_desktop_ready_id = 0; mce_log(LL_NOTICE, "desktop ready delay ended"); mdy_stm_schedule_rethink(); #ifdef ENABLE_CPU_GOVERNOR mdy_governor_rethink(); #endif } return FALSE; } /** Content of init-done flag file has changed * * @param path directory where flag file is * @param file name of the flag file * @param data (not used) */ static void mdy_flagfiles_init_done_cb(const char *path, const char *file, gpointer data) { (void)data; char full[256]; snprintf(full, sizeof full, "%s/%s", path, file); tristate_t prev = mdy_init_done; mdy_init_done = access(full, F_OK) ? TRISTATE_FALSE : TRISTATE_TRUE; if( mdy_init_done != prev ) { mce_log(LL_DEVEL, "init_done flag file present: %s -> %s", tristate_repr(prev), tristate_repr(mdy_init_done)); mdy_stm_schedule_rethink(); #ifdef ENABLE_CPU_GOVERNOR mdy_governor_rethink(); #endif mdy_poweron_led_rethink(); mdy_blanking_rethink_afterboot_delay(); /* broadcast change within mce */ datapipe_exec_full(&init_done_pipe, GINT_TO_POINTER(mdy_init_done)); } } /** Content of update-mode flag file has changed * * @param path directory where flag file is * @param file name of the flag file * @param data (not used) */ static void mdy_flagfiles_osupdate_running_cb(const char *path, const char *file, gpointer data) { (void)data; char full[256]; snprintf(full, sizeof full, "%s/%s", path, file); gboolean flag = access(full, F_OK) ? FALSE : TRUE; if( mdy_osupdate_running != flag ) { mdy_osupdate_running = flag; /* Log by default as it might help analyzing upgrade problems */ mce_log(LL_WARN, "osupdate_running flag file present: %s", mdy_osupdate_running ? "true" : "false"); if( mdy_osupdate_running ) { /* Issue display on request when update mode starts */ mce_datapipe_request_display_state(MCE_DISPLAY_ON); } /* suspend policy is affected by update mode */ mdy_stm_schedule_rethink(); /* blanking timers need to be started or stopped */ mdy_blanking_rethink_timers(true); /* broadcast change within mce */ datapipe_exec_full(&osupdate_running_pipe, GINT_TO_POINTER(mdy_osupdate_running)); } } /** Content of bootstate flag file has changed * * @param path directory where flag file is * @param file name of the flag file * @param data (not used) */ static void mdy_flagfiles_bootstate_cb(const char *path, const char *file, gpointer data) { (void)data; int fd = -1; char full[256]; char buff[256]; int rc; snprintf(full, sizeof full, "%s/%s", path, file); /* default to unknown */ mdy_bootstate = BOOTSTATE_UNKNOWN; if( (fd = open(full, O_RDONLY)) == -1 ) { if( errno != ENOENT ) mce_log(LL_WARN, "%s: %m", full); goto EXIT; } if( (rc = read(fd, buff, sizeof buff - 1)) == -1 ) { mce_log(LL_WARN, "%s: %m", full); goto EXIT; } buff[rc] = 0; buff[strcspn(buff, "\r\n")] = 0; mce_log(LL_NOTICE, "bootstate flag file content: %s", buff); /* for now we only need to differentiate USER and not USER */ if( !strcmp(buff, "BOOTSTATE=USER") ) mdy_bootstate = BOOTSTATE_USER; else mdy_bootstate = BOOTSTATE_ACT_DEAD; EXIT: if( fd != -1 ) close(fd); mdy_poweron_led_rethink(); mdy_blanking_rethink_afterboot_delay(); } /** Start tracking of init_done and bootstate flag files */ static void mdy_flagfiles_start_tracking(void) { static const char update_dir[] = "/tmp"; static const char update_flag[] = "os-update-running"; static const char flag_dir[] = "/run/systemd/boot-status"; static const char flag_init[] = "init-done"; static const char flag_boot[] = "bootstate"; time_t uptime = 0; // uptime in seconds time_t ready = 60; // desktop ready at time_t delay = 10; // default wait time /* if the update directory exits, track flag file presense */ if( access(update_dir, F_OK) == 0 ) { mdy_osupdate_running_watcher = filewatcher_create(update_dir, update_flag, mdy_flagfiles_osupdate_running_cb, 0, 0); } /* if the status directory exists, wait for flag file to appear */ if( access(flag_dir, F_OK) == 0 ) { mdy_init_done_watcher = filewatcher_create(flag_dir, flag_init, mdy_flagfiles_init_done_cb, 0, 0); mdy_bootstate_watcher = filewatcher_create(flag_dir, flag_boot, mdy_flagfiles_bootstate_cb, 0, 0); } /* or fall back to waiting for uptime to reach some minimum value */ if( !mdy_init_done_watcher ) { struct timespec ts; /* Assume that monotonic clock == uptime */ if( clock_gettime(CLOCK_MONOTONIC, &ts) == 0 ) uptime = ts.tv_sec; if( uptime + delay < ready ) delay = ready - uptime; /* do not wait for the init-done flag file */ if( mdy_init_done != TRISTATE_TRUE ) { mdy_init_done = TRISTATE_TRUE; datapipe_exec_full(&init_done_pipe, GINT_TO_POINTER(mdy_init_done)); } } mce_log(LL_NOTICE, "suspend delay %d seconds", (int)delay); mdy_desktop_ready_id = g_timeout_add_seconds(delay, mdy_flagfiles_desktop_ready_cb, 0); if( mdy_init_done_watcher ) { /* evaluate the initial state of init-done flag file */ filewatcher_force_trigger(mdy_init_done_watcher); } if( mdy_bootstate_watcher ) { /* evaluate the initial state of bootstate flag file */ filewatcher_force_trigger(mdy_bootstate_watcher); } else { /* or assume ACT_DEAD & co are not supported */ mdy_bootstate = BOOTSTATE_USER; } if( mdy_osupdate_running_watcher ) { /* evaluate the initial state of update-mode flag file */ filewatcher_force_trigger(mdy_osupdate_running_watcher); } } /** Stop tracking of init_done state */ static void mdy_flagfiles_stop_tracking(void) { filewatcher_delete(mdy_osupdate_running_watcher), mdy_osupdate_running_watcher = 0; filewatcher_delete(mdy_init_done_watcher), mdy_init_done_watcher = 0; filewatcher_delete(mdy_bootstate_watcher), mdy_bootstate_watcher = 0; if( mdy_desktop_ready_id ) { g_source_remove(mdy_desktop_ready_id); mdy_desktop_ready_id = 0; } } /* ========================================================================= * * DYNAMIC_SETTINGS * ========================================================================= */ /** * GConf callback for display related settings * * @param gcc Unused * @param id Connection ID from gconf_client_notify_add() * @param entry The modified GConf entry * @param data Unused */ static void mdy_setting_cb(GConfClient *const gcc, const guint id, GConfEntry *const entry, gpointer const data) { const GConfValue *gcv = gconf_entry_get_value(entry); (void)gcc; (void)data; /* Key is unset */ if (gcv == NULL) { mce_log(LL_DEBUG, "GConf Key `%s' has been unset", gconf_entry_get_key(entry)); goto EXIT; } if( id == mdy_brightness_setting_setting_id ) { gint val = gconf_value_get_int(gcv); if( mdy_brightness_setting != val ) { mce_log(LL_NOTICE, "mdy_brightness_setting: %d -> %d", mdy_brightness_setting, val); mdy_brightness_setting = val; /* Save timestamp of the setting change */ mdy_brightness_setting_change_time = mce_lib_get_boot_tick(); mdy_setting_sanitize_brightness_levels(); } } else if( id == mdy_brightness_dim_static_setting_id ) { gint val = gconf_value_get_int(gcv); if( mdy_brightness_dim_static != val ) { mce_log(LL_NOTICE, "mdy_brightness_dim_static: %d -> %d", mdy_brightness_dim_static, val); mdy_brightness_dim_static = val; mdy_setting_sanitize_brightness_levels(); mdy_brightness_set_dim_level(); } } else if( id == mdy_brightness_dim_dynamic_setting_id ) { gint val = gconf_value_get_int(gcv); if( mdy_brightness_dim_dynamic != val ) { mce_log(LL_NOTICE, "mdy_brightness_dim_dynamic: %d -> %d", mdy_brightness_dim_dynamic, val); mdy_brightness_dim_dynamic = val; mdy_setting_sanitize_brightness_levels(); mdy_brightness_set_dim_level(); } } else if( id == mdy_brightness_dim_compositor_lo_setting_id ) { gint val = gconf_value_get_int(gcv); if( mdy_brightness_dim_compositor_lo != val ) { mce_log(LL_NOTICE, "mdy_brightness_dim_compositor_lo: %d -> %d", mdy_brightness_dim_compositor_lo, val); mdy_brightness_dim_compositor_lo = val; mdy_setting_sanitize_brightness_levels(); mdy_brightness_set_dim_level(); } } else if( id == mdy_brightness_dim_compositor_hi_setting_id ) { gint val = gconf_value_get_int(gcv); if( mdy_brightness_dim_compositor_hi != val ) { mce_log(LL_NOTICE, "mdy_brightness_dim_compositor_hi: %d -> %d", mdy_brightness_dim_compositor_hi, val); mdy_brightness_dim_compositor_hi = val; mdy_setting_sanitize_brightness_levels(); mdy_brightness_set_dim_level(); } } else if( id == mdy_automatic_brightness_setting_setting_id ) { /* Save timestamp of the setting change */ mdy_brightness_setting_change_time = mce_lib_get_boot_tick(); } else if( id == mdy_brightness_step_size_setting_id ) { // NOTE: This is not supposed to be changed at runtime gint val = gconf_value_get_int(gcv); if( mdy_brightness_step_size != val ) { mce_log(LL_WARN, "mdy_brightness_step_size: %d -> %d", mdy_brightness_step_size, val); mdy_brightness_step_size = val; mdy_setting_sanitize_brightness_levels(); } } else if( id == mdy_brightness_step_count_setting_id ) { // NOTE: This is not supposed to be changed at runtime gint val = gconf_value_get_int(gcv); if( mdy_brightness_step_count != val ) { mce_log(LL_WARN, "mdy_brightness_step_count: %d -> %d", mdy_brightness_step_count, val); mdy_brightness_step_count = val; mdy_setting_sanitize_brightness_levels(); } } else if (id == mdy_blank_timeout_setting_id) { mdy_blank_timeout = gconf_value_get_int(gcv); mdy_blanking_update_device_inactive_delay(); /* Reprogram blanking timers */ mdy_blanking_rethink_timers(true); } else if( id == mdy_blank_from_lockscreen_timeout_setting_id ) { mdy_blank_from_lockscreen_timeout = gconf_value_get_int(gcv); /* Reprogram blanking timers */ mdy_blanking_rethink_timers(true); } else if( id == mdy_blank_from_lpm_on_timeout_setting_id ) { mdy_blank_from_lpm_on_timeout = gconf_value_get_int(gcv); /* Reprogram blanking timers */ mdy_blanking_rethink_timers(true); } else if( id == mdy_blank_from_lpm_off_timeout_setting_id ) { mdy_blank_from_lpm_off_timeout = gconf_value_get_int(gcv); /* Reprogram blanking timers */ mdy_blanking_rethink_timers(true); } else if (id == mdy_use_low_power_mode_setting_id) { mdy_use_low_power_mode = gconf_value_get_bool(gcv); if (((display_state_curr == MCE_DISPLAY_LPM_OFF) || (display_state_curr == MCE_DISPLAY_LPM_ON)) && ((mdy_low_power_mode_supported == FALSE) || (mdy_use_low_power_mode == FALSE) || (mdy_blanking_can_blank_from_low_power_mode() == TRUE))) { mce_datapipe_request_display_state(MCE_DISPLAY_OFF); } else if ((display_state_curr == MCE_DISPLAY_OFF) && (mdy_use_low_power_mode == TRUE) && (mdy_blanking_can_blank_from_low_power_mode() == FALSE) && (mdy_low_power_mode_supported == TRUE)) { mce_datapipe_request_display_state(MCE_DISPLAY_LPM_ON); } } else if (id == mdy_adaptive_dimming_enabled_setting_id) { mdy_adaptive_dimming_enabled = gconf_value_get_bool(gcv); mdy_blanking_reset_adaptive_dimming_delay(); mdy_blanking_prime_adaptive_dimming(); } else if (id == mdy_adaptive_dimming_threshold_setting_id) { mdy_adaptive_dimming_threshold = gconf_value_get_int(gcv); mdy_blanking_prime_adaptive_dimming(); } else if (id == mdy_possible_dim_timeouts_setting_id ) { mdy_setting_sanitize_dim_timeouts(true); /* Reprogram blanking timers */ mdy_blanking_rethink_timers(true); } else if (id == mdy_disp_dim_timeout_default_setting_id) { mdy_disp_dim_timeout_default = gconf_value_get_int(gcv); mdy_setting_sanitize_dim_timeouts(false); /* Reprogram blanking timers */ mdy_blanking_rethink_timers(true); } else if (id == mdy_disp_dim_timeout_keyboard_setting_id) { mdy_disp_dim_timeout_keyboard = gconf_value_get_int(gcv); mdy_setting_sanitize_dim_timeouts(false); /* Reprogram blanking timers */ mdy_blanking_rethink_timers(true); } else if (id == mdy_blanking_inhibit_mode_setting_id) { mdy_blanking_inhibit_mode = gconf_value_get_int(gcv); /* force blanking reprogramming */ mdy_blanking_rethink_timers(true); } else if (id == mdy_kbd_slide_inhibit_mode_setting_id) { mdy_kbd_slide_inhibit_mode = gconf_value_get_int(gcv); /* force blanking reprogramming */ mdy_blanking_rethink_timers(true); } else if( id == mdy_disp_never_blank_setting_id ) { gint prev = mdy_disp_never_blank; mdy_disp_never_blank = gconf_value_get_int(gcv); if( prev != mdy_disp_never_blank ) { mce_log(LL_NOTICE, "never_blank = %d", mdy_disp_never_blank); if( mdy_disp_never_blank ) mce_datapipe_request_display_state(MCE_DISPLAY_ON); mdy_blanking_rethink_timers(true); } } else if( id == mdy_compositor_core_delay_setting_id ) { mdy_compositor_core_delay = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "compositor kill delay = %d", mdy_compositor_core_delay); } else if( id == mdy_brightness_fade_duration_def_ms_setting_id ) { mdy_brightness_fade_duration_def_ms = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "fade duration / def = %d", mdy_brightness_fade_duration_def_ms); } else if( id == mdy_brightness_fade_duration_dim_ms_setting_id ) { mdy_brightness_fade_duration_dim_ms = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "fade duration / dim = %d", mdy_brightness_fade_duration_dim_ms); } else if( id == mdy_brightness_fade_duration_als_ms_setting_id ) { mdy_brightness_fade_duration_als_ms = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "fade duration / als = %d", mdy_brightness_fade_duration_als_ms); } else if( id == mdy_brightness_fade_duration_blank_ms_setting_id ) { mdy_brightness_fade_duration_blank_ms = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "fade duration / blank = %d", mdy_brightness_fade_duration_blank_ms); } else if( id == mdy_brightness_fade_duration_unblank_ms_setting_id ) { mdy_brightness_fade_duration_unblank_ms = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "fade duration / unblank = %d", mdy_brightness_fade_duration_unblank_ms); } else if( id == mdy_dbus_display_off_override_setting_id ) { mdy_dbus_display_off_override = gconf_value_get_int(gcv); mce_log(LL_NOTICE, "display off override = %d", mdy_dbus_display_off_override); } else if( id == mdy_blanking_pause_mode_setting_id ) { gint old = mdy_blanking_pause_mode; mdy_blanking_pause_mode = gconf_value_get_int(gcv); if( mdy_blanking_pause_mode != old ) { mce_log(LL_NOTICE, "blanking pause mode = %s", blanking_pause_mode_repr(mdy_blanking_pause_mode)); mdy_blanking_pause_evaluate_allowed(); } } else if( id == mdy_blanking_from_tklock_disabled_setting_id ) { gint old = mdy_blanking_from_tklock_disabled; mdy_blanking_from_tklock_disabled = gconf_value_get_int(gcv); if( mdy_blanking_from_tklock_disabled != old ) { mce_log(LL_NOTICE, "blanking from lockscreen disabled: %d -> %d", old, mdy_blanking_from_tklock_disabled); mdy_blanking_rethink_timers(true); } } else if (id == mdy_orientation_sensor_enabled_setting_id) { mdy_orientation_sensor_enabled = gconf_value_get_bool(gcv); mdy_orientation_sensor_rethink(); } else if (id == mdy_flipover_gesture_enabled_setting_id) { mdy_flipover_gesture_enabled = gconf_value_get_bool(gcv); mdy_orientation_sensor_rethink(); } else if (id == mdy_orientation_change_is_activity_setting_id) { mdy_orientation_change_is_activity = gconf_value_get_bool(gcv); mdy_orientation_sensor_rethink(); } else { mce_log(LL_WARN, "Spurious GConf value received; confused!"); } EXIT: return; } static void mdy_setting_sanitize_brightness_levels(void) { /* During settings reset operation all affected settings are * first then change notifications are emitted one by one. * * This means the locally cached values do not get updated * simultaneously via notification callbacks. But we can * explicitly update all three brightness related settings * which allows us to see all three values change at once * already when the first notification is received (and the * possible notifications for other two values do not cause * any further activity). */ mce_setting_get_int(MCE_SETTING_DISPLAY_BRIGHTNESS_LEVEL_COUNT, &mdy_brightness_step_count); mce_setting_get_int(MCE_SETTING_DISPLAY_BRIGHTNESS_LEVEL_SIZE, &mdy_brightness_step_size); mce_setting_get_int(MCE_SETTING_DISPLAY_BRIGHTNESS, &mdy_brightness_setting); /* Migrate configuration ranges */ if( mdy_brightness_step_count == 5 && mdy_brightness_step_size == 1 ) { /* Legacy 5 step control -> convert to percentage */ mdy_brightness_step_count = 100; mdy_brightness_step_size = 1; mdy_brightness_setting = 20 * mdy_brightness_setting; } else if( mdy_brightness_step_count != 100 || mdy_brightness_step_size != 1 ) { /* Unsupported config -> force to 60 percent */ mdy_brightness_step_count = 100; mdy_brightness_step_size = 1; mdy_brightness_setting = 60; } /* Clip brightness to supported range */ if( mdy_brightness_setting > 100 ) mdy_brightness_setting = 100; else if( mdy_brightness_setting < 1 ) mdy_brightness_setting = 1; /* Clip dimmed brightness settings to supported range */ mdy_brightness_dim_static = mce_clip_int(1, 100, mdy_brightness_dim_static); mdy_brightness_dim_dynamic = mce_clip_int(1, 100, mdy_brightness_dim_dynamic); mdy_brightness_dim_compositor_lo = mce_clip_int(0, 100, mdy_brightness_dim_compositor_lo); mdy_brightness_dim_compositor_hi = mce_clip_int(0, 100, mdy_brightness_dim_compositor_hi); /* Update config; signals will be emitted and config notifiers * called - mdy_setting_cb() must ignore no-change notifications * to avoid recursive sanitation. */ mce_setting_set_int(MCE_SETTING_DISPLAY_BRIGHTNESS_LEVEL_SIZE, mdy_brightness_step_size); mce_setting_set_int(MCE_SETTING_DISPLAY_BRIGHTNESS_LEVEL_COUNT, mdy_brightness_step_count); mce_setting_set_int(MCE_SETTING_DISPLAY_BRIGHTNESS, mdy_brightness_setting); mce_log(LL_DEBUG, "mdy_brightness_setting=%d", mdy_brightness_setting); mce_setting_set_int(MCE_SETTING_DISPLAY_DIM_STATIC_BRIGHTNESS, mdy_brightness_dim_static); mce_setting_set_int(MCE_SETTING_DISPLAY_DIM_DYNAMIC_BRIGHTNESS, mdy_brightness_dim_dynamic); mce_setting_set_int(MCE_SETTING_DISPLAY_DIM_COMPOSITOR_LO, mdy_brightness_dim_compositor_lo); mce_setting_set_int(MCE_SETTING_DISPLAY_DIM_COMPOSITOR_HI, mdy_brightness_dim_compositor_hi); mce_log(LL_DEBUG, "mdy_brightness_dim_static=%d", mdy_brightness_dim_static); mce_log(LL_DEBUG, "mdy_brightness_dim_dynamic=%d", mdy_brightness_dim_dynamic); mce_log(LL_DEBUG, "mdy_brightness_dim_compositor_lo=%d", mdy_brightness_dim_compositor_lo); mce_log(LL_DEBUG, "mdy_brightness_dim_compositor_hi=%d", mdy_brightness_dim_compositor_hi); mdy_datapipe_execute_brightness(); } static void mdy_setting_sanitize_dim_timeouts(bool force_update) { /* If asked to, flush existing list of allowed timeouts */ if( force_update && mdy_possible_dim_timeouts ) { g_slist_free(mdy_possible_dim_timeouts), mdy_possible_dim_timeouts = 0; } /* Make sure we have a list of allowed timeouts */ if( !mdy_possible_dim_timeouts ) { mce_setting_get_int_list(MCE_SETTING_DISPLAY_DIM_TIMEOUT_LIST, &mdy_possible_dim_timeouts); } if( !mdy_possible_dim_timeouts ) { static const int def[] = { MCE_DEFAULT_DISPLAY_DIM_TIMEOUT_LIST }; GSList *tmp = 0; for( size_t i = 0; i < G_N_ELEMENTS(def); ++i ) tmp = g_slist_prepend(tmp, GINT_TO_POINTER(def[i])); mdy_possible_dim_timeouts = g_slist_reverse(tmp); } /* Reset adaptive dimming state */ mdy_blanking_reset_adaptive_dimming_delay(); /* Update inactivity timeout */ mdy_blanking_update_device_inactive_delay(); } /** Get initial setting values and start tracking changes */ static void mdy_setting_init(void) { /* Display brightness settings */ mce_setting_track_int(MCE_SETTING_DISPLAY_BRIGHTNESS_LEVEL_COUNT, &mdy_brightness_step_count, MCE_DEFAULT_DISPLAY_BRIGHTNESS_LEVEL_COUNT, mdy_setting_cb, &mdy_brightness_step_count_setting_id); mce_setting_track_int(MCE_SETTING_DISPLAY_BRIGHTNESS_LEVEL_SIZE, &mdy_brightness_step_size, MCE_DEFAULT_DISPLAY_BRIGHTNESS_LEVEL_SIZE, mdy_setting_cb, &mdy_brightness_step_size_setting_id); mce_setting_track_int(MCE_SETTING_DISPLAY_BRIGHTNESS, &mdy_brightness_setting, MCE_DEFAULT_DISPLAY_BRIGHTNESS, mdy_setting_cb, &mdy_brightness_setting_setting_id); mce_setting_track_int(MCE_SETTING_DISPLAY_DIM_STATIC_BRIGHTNESS, &mdy_brightness_dim_static, MCE_DEFAULT_DISPLAY_DIM_STATIC_BRIGHTNESS, mdy_setting_cb, &mdy_brightness_dim_static_setting_id); mce_setting_track_int(MCE_SETTING_DISPLAY_DIM_DYNAMIC_BRIGHTNESS, &mdy_brightness_dim_dynamic, MCE_DEFAULT_DISPLAY_DIM_DYNAMIC_BRIGHTNESS, mdy_setting_cb, &mdy_brightness_dim_dynamic_setting_id); mce_setting_track_int(MCE_SETTING_DISPLAY_DIM_COMPOSITOR_LO, &mdy_brightness_dim_compositor_lo, MCE_DEFAULT_DISPLAY_DIM_COMPOSITOR_LO, mdy_setting_cb, &mdy_brightness_dim_compositor_lo_setting_id); mce_setting_track_int(MCE_SETTING_DISPLAY_DIM_COMPOSITOR_HI, &mdy_brightness_dim_compositor_hi, MCE_DEFAULT_DISPLAY_DIM_COMPOSITOR_HI, mdy_setting_cb, &mdy_brightness_dim_compositor_hi_setting_id); /* Note: We're only interested in auto-brightness change * notifications. The value itself is handled in * filter-brightness-als plugin. */ mce_setting_notifier_add(MCE_SETTING_DISPLAY_PATH, MCE_SETTING_DISPLAY_ALS_ENABLED, mdy_setting_cb, &mdy_automatic_brightness_setting_setting_id); /* Migrate ranges, update hw dim/on brightness levels */ mdy_setting_sanitize_brightness_levels(); /* Display blank timeout */ mce_setting_track_int(MCE_SETTING_DISPLAY_BLANK_TIMEOUT, &mdy_blank_timeout, MCE_DEFAULT_DISPLAY_BLANK_TIMEOUT, mdy_setting_cb, &mdy_blank_timeout_setting_id); /* Display blank from lockscreen timeout */ mce_setting_track_int(MCE_SETTING_DISPLAY_BLANK_FROM_LOCKSCREEN_TIMEOUT, &mdy_blank_from_lockscreen_timeout, MCE_DEFAULT_DISPLAY_BLANK_FROM_LOCKSCREEN_TIMEOUT, mdy_setting_cb, &mdy_blank_from_lockscreen_timeout_setting_id); /* Display blank from lpm on timeout */ mce_setting_track_int(MCE_SETTING_DISPLAY_BLANK_FROM_LPM_ON_TIMEOUT, &mdy_blank_from_lpm_on_timeout, MCE_DEFAULT_DISPLAY_BLANK_FROM_LPM_ON_TIMEOUT, mdy_setting_cb, &mdy_blank_from_lpm_on_timeout_setting_id); /* Display blank from lpm off timeout */ mce_setting_track_int(MCE_SETTING_DISPLAY_BLANK_FROM_LPM_OFF_TIMEOUT, &mdy_blank_from_lpm_off_timeout, MCE_DEFAULT_DISPLAY_BLANK_FROM_LPM_OFF_TIMEOUT, mdy_setting_cb, &mdy_blank_from_lpm_off_timeout_setting_id); /* Never blank toggle */ mce_setting_track_int(MCE_SETTING_DISPLAY_NEVER_BLANK, &mdy_disp_never_blank, MCE_DEFAULT_DISPLAY_NEVER_BLANK, mdy_setting_cb, &mdy_disp_never_blank_setting_id); /* Use adaptive display dim timeout toggle */ mce_setting_track_bool(MCE_SETTING_DISPLAY_ADAPTIVE_DIMMING, &mdy_adaptive_dimming_enabled, MCE_DEFAULT_DISPLAY_ADAPTIVE_DIMMING, mdy_setting_cb, &mdy_adaptive_dimming_enabled_setting_id); /* Adaptive display dimming threshold timer */ mce_setting_track_int(MCE_SETTING_DISPLAY_ADAPTIVE_DIM_THRESHOLD, &mdy_adaptive_dimming_threshold, MCE_DEFAULT_DISPLAY_ADAPTIVE_DIM_THRESHOLD, mdy_setting_cb, &mdy_adaptive_dimming_threshold_setting_id); /* Display dim timer */ mce_setting_track_int(MCE_SETTING_DISPLAY_DIM_TIMEOUT, &mdy_disp_dim_timeout_default, MCE_DEFAULT_DISPLAY_DIM_TIMEOUT, mdy_setting_cb, &mdy_disp_dim_timeout_default_setting_id); mce_setting_track_int(MCE_SETTING_DISPLAY_DIM_WITH_KEYBOARD_TIMEOUT, &mdy_disp_dim_timeout_keyboard, MCE_DEFAULT_DISPLAY_DIM_WITH_KEYBOARD_TIMEOUT, mdy_setting_cb, &mdy_disp_dim_timeout_keyboard_setting_id); /* Possible dim timeouts */ mce_setting_notifier_add(MCE_SETTING_DISPLAY_PATH, MCE_SETTING_DISPLAY_DIM_TIMEOUT_LIST, mdy_setting_cb, &mdy_possible_dim_timeouts_setting_id); /* After all blanking and dimming settings are fetched, we * need to sanitize them and calculate inactivity timeout */ mdy_setting_sanitize_dim_timeouts(true); /* Use low power mode toggle */ mce_setting_track_bool(MCE_SETTING_USE_LOW_POWER_MODE, &mdy_use_low_power_mode, MCE_DEFAULT_USE_LOW_POWER_MODE, mdy_setting_cb, &mdy_use_low_power_mode_setting_id); /* Blanking inhibit modes */ mce_setting_track_int(MCE_SETTING_BLANKING_INHIBIT_MODE, &mdy_blanking_inhibit_mode, MCE_DEFAULT_BLANKING_INHIBIT_MODE, mdy_setting_cb, &mdy_blanking_inhibit_mode_setting_id); mce_setting_track_int(MCE_SETTING_KBD_SLIDE_INHIBIT, &mdy_kbd_slide_inhibit_mode, MCE_DEFAULT_KBD_SLIDE_INHIBIT, mdy_setting_cb, &mdy_kbd_slide_inhibit_mode_setting_id); /* Delay for killing unresponsive compositor */ mce_setting_track_int(MCE_SETTING_LIPSTICK_CORE_DELAY, &mdy_compositor_core_delay, MCE_DEFAULT_LIPSTICK_CORE_DELAY, mdy_setting_cb, &mdy_compositor_core_delay_setting_id); /* Brightness fade length: default */ mce_setting_track_int(MCE_SETTING_BRIGHTNESS_FADE_DEFAULT_MS, &mdy_brightness_fade_duration_def_ms, MCE_DEFAULT_BRIGHTNESS_FADE_DEFAULT_MS, mdy_setting_cb, &mdy_brightness_fade_duration_def_ms_setting_id); /* Brightness fade length: dim */ mce_setting_track_int(MCE_SETTING_BRIGHTNESS_FADE_DIMMING_MS, &mdy_brightness_fade_duration_dim_ms, MCE_DEFAULT_BRIGHTNESS_FADE_DIMMING_MS, mdy_setting_cb, &mdy_brightness_fade_duration_dim_ms_setting_id); /* Brightness fade length: als */ mce_setting_track_int(MCE_SETTING_BRIGHTNESS_FADE_ALS_MS, &mdy_brightness_fade_duration_als_ms, MCE_DEFAULT_BRIGHTNESS_FADE_ALS_MS, mdy_setting_cb, &mdy_brightness_fade_duration_als_ms_setting_id); /* Brightness fade length: blank */ mce_setting_track_int(MCE_SETTING_BRIGHTNESS_FADE_BLANK_MS, &mdy_brightness_fade_duration_blank_ms, MCE_DEFAULT_BRIGHTNESS_FADE_BLANK_MS, mdy_setting_cb, &mdy_brightness_fade_duration_blank_ms_setting_id); /* Brightness fade length: unblank */ mce_setting_track_int(MCE_SETTING_BRIGHTNESS_FADE_UNBLANK_MS, &mdy_brightness_fade_duration_unblank_ms, MCE_DEFAULT_BRIGHTNESS_FADE_UNBLANK_MS, mdy_setting_cb, &mdy_brightness_fade_duration_unblank_ms_setting_id); /* Override mode for display off requests made over D-Bus */ mce_setting_track_int(MCE_SETTING_DISPLAY_OFF_OVERRIDE, &mdy_dbus_display_off_override, MCE_DEFAULT_DISPLAY_OFF_OVERRIDE, mdy_setting_cb, &mdy_dbus_display_off_override_setting_id); /* Use orientation sensor */ mce_setting_track_bool(MCE_SETTING_ORIENTATION_SENSOR_ENABLED, &mdy_orientation_sensor_enabled, MCE_DEFAULT_ORIENTATION_SENSOR_ENABLED, mdy_setting_cb, &mdy_orientation_sensor_enabled_setting_id); mce_setting_track_bool(MCE_SETTING_FLIPOVER_GESTURE_ENABLED, &mdy_flipover_gesture_enabled, MCE_DEFAULT_FLIPOVER_GESTURE_ENABLED, mdy_setting_cb, &mdy_flipover_gesture_enabled_setting_id); mce_setting_track_bool(MCE_SETTING_ORIENTATION_CHANGE_IS_ACTIVITY, &mdy_orientation_change_is_activity, MCE_DEFAULT_ORIENTATION_CHANGE_IS_ACTIVITY, mdy_setting_cb, &mdy_orientation_change_is_activity_setting_id); /* Blanking pause mode */ mce_setting_track_int(MCE_SETTING_DISPLAY_BLANKING_PAUSE_MODE, &mdy_blanking_pause_mode, MCE_DEFAULT_DISPLAY_BLANKING_PAUSE_MODE, mdy_setting_cb, &mdy_blanking_pause_mode_setting_id); /* Config tracking for disabling automatic screen dimming/blanking * while showing lockscreen. */ mce_setting_track_int(MCE_SETTING_TK_AUTO_BLANK_DISABLE, &mdy_blanking_from_tklock_disabled, MCE_DEFAULT_TK_AUTO_BLANK_DISABLE, mdy_setting_cb, &mdy_blanking_from_tklock_disabled_setting_id); } static void mdy_setting_quit(void) { /* Remove config change notifiers */ mce_setting_notifier_remove(mdy_brightness_step_count_setting_id), mdy_brightness_step_count_setting_id = 0; mce_setting_notifier_remove(mdy_brightness_step_size_setting_id), mdy_brightness_step_size_setting_id = 0; mce_setting_notifier_remove(mdy_brightness_setting_setting_id), mdy_brightness_setting_setting_id = 0; mce_setting_notifier_remove(mdy_brightness_dim_static_setting_id), mdy_brightness_dim_static_setting_id = 0; mce_setting_notifier_remove(mdy_brightness_dim_dynamic_setting_id), mdy_brightness_dim_dynamic_setting_id = 0; mce_setting_notifier_remove(mdy_brightness_dim_compositor_lo_setting_id), mdy_brightness_dim_compositor_lo_setting_id = 0; mce_setting_notifier_remove(mdy_brightness_dim_compositor_hi_setting_id), mdy_brightness_dim_compositor_hi_setting_id = 0; mce_setting_notifier_remove(mdy_automatic_brightness_setting_setting_id), mdy_automatic_brightness_setting_setting_id = 0; mce_setting_notifier_remove(mdy_blank_timeout_setting_id), mdy_blank_timeout_setting_id = 0; mce_setting_notifier_remove(mdy_blank_from_lockscreen_timeout_setting_id), mdy_blank_from_lockscreen_timeout_setting_id = 0; mce_setting_notifier_remove(mdy_blank_from_lpm_on_timeout_setting_id), mdy_blank_from_lpm_on_timeout_setting_id = 0; mce_setting_notifier_remove(mdy_blank_from_lpm_off_timeout_setting_id), mdy_blank_from_lpm_off_timeout_setting_id = 0; mce_setting_notifier_remove(mdy_disp_never_blank_setting_id), mdy_disp_never_blank_setting_id = 0; mce_setting_notifier_remove(mdy_adaptive_dimming_enabled_setting_id), mdy_adaptive_dimming_enabled_setting_id = 0; mce_setting_notifier_remove(mdy_adaptive_dimming_threshold_setting_id), mdy_adaptive_dimming_threshold_setting_id = 0; mce_setting_notifier_remove(mdy_disp_dim_timeout_default_setting_id), mdy_disp_dim_timeout_default_setting_id = 0; mce_setting_notifier_remove(mdy_disp_dim_timeout_keyboard_setting_id), mdy_disp_dim_timeout_keyboard_setting_id = 0; mce_setting_notifier_remove(mdy_possible_dim_timeouts_setting_id), mdy_possible_dim_timeouts_setting_id = 0; mce_setting_notifier_remove(mdy_use_low_power_mode_setting_id), mdy_use_low_power_mode_setting_id = 0; mce_setting_notifier_remove(mdy_blanking_inhibit_mode_setting_id), mdy_blanking_inhibit_mode_setting_id = 0; mce_setting_notifier_remove(mdy_kbd_slide_inhibit_mode_setting_id), mdy_kbd_slide_inhibit_mode_setting_id = 0; mce_setting_notifier_remove(mdy_compositor_core_delay_setting_id), mdy_compositor_core_delay_setting_id = 0; mce_setting_notifier_remove(mdy_brightness_fade_duration_def_ms_setting_id), mdy_brightness_fade_duration_def_ms_setting_id = 0; mce_setting_notifier_remove(mdy_brightness_fade_duration_dim_ms_setting_id), mdy_brightness_fade_duration_dim_ms_setting_id = 0; mce_setting_notifier_remove(mdy_brightness_fade_duration_als_ms_setting_id), mdy_brightness_fade_duration_als_ms_setting_id = 0; mce_setting_notifier_remove(mdy_brightness_fade_duration_blank_ms_setting_id), mdy_brightness_fade_duration_blank_ms_setting_id = 0; mce_setting_notifier_remove(mdy_brightness_fade_duration_unblank_ms_setting_id), mdy_brightness_fade_duration_unblank_ms_setting_id = 0; mce_setting_notifier_remove(mdy_dbus_display_off_override_setting_id), mdy_dbus_display_off_override_setting_id = 0; mce_setting_notifier_remove(mdy_orientation_sensor_enabled_setting_id), mdy_orientation_sensor_enabled_setting_id = 0; mce_setting_notifier_remove(mdy_flipover_gesture_enabled_setting_id), mdy_flipover_gesture_enabled_setting_id = 0; mce_setting_notifier_remove(mdy_orientation_change_is_activity_setting_id), mdy_orientation_change_is_activity_setting_id = 0; mce_setting_notifier_remove(mdy_blanking_pause_mode_setting_id), mdy_blanking_pause_mode_setting_id = 0; mce_setting_notifier_remove(mdy_blanking_from_tklock_disabled_setting_id), mdy_blanking_from_tklock_disabled_setting_id = 0; /* Free dynamic data obtained from config */ g_slist_free(mdy_possible_dim_timeouts), mdy_possible_dim_timeouts = 0; } /* ========================================================================= * * MODULE_LOAD_UNLOAD * ========================================================================= */ /** Probe maximum and current backlight brightness from sysfs */ static void mdy_brightness_init(void) { gulong tmp = 0; /* If possible, obtain maximum brightness level */ if( !mdy_brightness_level_maximum_path ) { mce_log(LL_NOTICE, "No path for maximum brightness file; " "defaulting to %d", mdy_brightness_level_maximum); } else if( !mce_read_number_string_from_file(mdy_brightness_level_maximum_path, &tmp, NULL, FALSE, TRUE) ) { mce_log(LL_ERR, "Could not read the maximum brightness from %s; " "defaulting to %d", mdy_brightness_level_maximum_path, mdy_brightness_level_maximum); } else mdy_brightness_level_maximum = (gint)tmp; mce_log(LL_DEBUG, "max_brightness = %d", mdy_brightness_level_maximum); /* If we can read the current hw brightness level, update the * cached brightness so we can do soft transitions from the * initial state */ if( mdy_brightness_level_output.path && mce_read_number_string_from_file(mdy_brightness_level_output.path, &tmp, NULL, FALSE, TRUE) ) { mdy_brightness_level_active = mdy_brightness_level_cached = (gint)tmp; } mce_log(LL_DEBUG, "mdy_brightness_level_active=%d", mdy_brightness_level_active); /* On some devices there are multiple ways to control backlight * brightness. We use only one, but after bootup it might contain * a value that does not match the reality. * * The likely scenario is something like: * lcd-backlight/brightness = 255 (incorrect) * wled/brightness = 64 (correct) * * Which leads - if using manual/100% brightness - mce not to * update the brightness because it already is supposed to * be at 255. * * Using "reported_by_kernel minus one" as mce cached value * would make mce to update the sysfs value later on, but then * the kernel can ignore it because it sees no change. * * But by writing the off-by-one value to sysfs: * a) we're still close to the reported value in case it happened * to be correct (after mce restart) * b) the kernel side sees at least one brightness change even if * the brightness setting evaluation would lead to the same * value that was originally reported */ if( mdy_brightness_level_active > 0 ) mdy_brightness_force_level(mdy_brightness_level_active - 1); } /** * Init function for the display handling module * * @param module Unused * * @return NULL on success, a string with an error message on failure */ const gchar *g_module_check_init(GModule *module) { const gchar *failure = 0; gboolean display_is_on = TRUE; (void)module; compositor_stm_init_config(); mdy_blanking_init_pause_client_tracking(); mdy_compositor_init(); /* Allow execution of worker thread jobs from this plugin */ mce_worker_add_context(MODULE_NAME); /* Initialise the display type and the relevant paths */ mdy_display_type_get(); #ifdef ENABLE_CPU_GOVERNOR /* Get CPU scaling governor settings from INI-files */ mdy_governor_default = mdy_governor_get_settings("Default"); mdy_governor_interactive = mdy_governor_get_settings("Interactive"); /* Get cpu scaling governor configuration & track changes */ mce_setting_track_int(MCE_SETTING_CPU_SCALING_GOVERNOR, &mdy_governor_conf, MCE_DEFAULT_CPU_SCALING_GOVERNOR, mdy_governor_setting_cb, &mdy_governor_conf_setting_id); /* Evaluate initial state */ mdy_governor_rethink(); #endif #ifdef ENABLE_WAKELOCKS /* Get autosuspend policy configuration & track changes */ mce_setting_track_int(MCE_SETTING_USE_AUTOSUSPEND, &mdy_suspend_policy, MCE_DEFAULT_USE_AUTOSUSPEND, mdy_autosuspend_setting_cb, &mdy_suspend_policy_setting_id); /* Evaluate initial state */ mdy_stm_schedule_rethink(); #endif /* Start waiting for init_done state */ mdy_flagfiles_start_tracking(); /* Append triggers/filters to datapipes */ mdy_datapipe_init(); /* Install dbus message handlers */ mdy_dbus_init(); /* Probe maximum and current backlight brightness from sysfs */ mdy_brightness_init(); /* Get initial setting values and start tracking changes */ mdy_setting_init(); mdy_cabc_mode_set(DEFAULT_CABC_MODE); /* if we have brightness control file and initial brightness * is zero -> start from display off */ if( mdy_brightness_level_output.path && mdy_brightness_level_cached <= 0 ) display_is_on = FALSE; /* Note: Transition to MCE_DISPLAY_OFF can be made already * here, but the MCE_DISPLAY_ON state is blocked until mCE * gets notification from DSME */ mce_log(LL_INFO, "initial display mode = %s", display_is_on ? "ON" : "OFF"); mce_datapipe_request_display_state(display_is_on ? MCE_DISPLAY_ON : MCE_DISPLAY_OFF); /* Start the framebuffer sleep/wakeup thread */ #ifdef ENABLE_WAKELOCKS mdy_waitfb_thread_start(&mdy_waitfb_data); #endif /* Re-evaluate the power on LED state from idle callback * i.e. when the led plugin is loaded and operational */ mdy_poweron_led_rethink_schedule(); /* Evaluate initial orientation sensor enable state */ mdy_orientation_sensor_rethink(); /* Send initial blanking pause & inhibit states */ mdy_dbus_send_blanking_pause_status(0); mdy_dbus_send_blanking_inhibit_status(0); return failure; } /** * Exit function for the display handling module * * @todo D-Bus unregistration * * @param module Unused */ void g_module_unload(GModule *module) { (void)module; /* Mark down that we are unloading */ mdy_unloading_module = TRUE; mdy_blanking_quit_pause_client_tracking(); /* Deny execution of worker thread jobs from this plugin */ mce_worker_rem_context(MODULE_NAME); /* Kill the framebuffer sleep/wakeup thread */ #ifdef ENABLE_WAKELOCKS mdy_waitfb_thread_stop(&mdy_waitfb_data); #endif /* Remove dbus message handlers */ mdy_dbus_quit(); /* Stop tracking setting changes */ mdy_setting_quit(); /* Stop waiting for init_done state */ mdy_flagfiles_stop_tracking(); #ifdef ENABLE_WAKELOCKS /* Remove suspend policy change notifier */ mce_setting_notifier_remove(mdy_suspend_policy_setting_id), mdy_suspend_policy_setting_id = 0; #endif #ifdef ENABLE_CPU_GOVERNOR /* Remove cpu scaling governor change notifier */ mce_setting_notifier_remove(mdy_governor_conf_setting_id), mdy_governor_conf_setting_id = 0; /* Switch back to defaults */ mdy_governor_rethink(); /* Release CPU scaling governor settings from INI-files */ mdy_governor_free_settings(mdy_governor_default), mdy_governor_default = 0; mdy_governor_free_settings(mdy_governor_interactive), mdy_governor_interactive = 0; #endif /* Remove triggers/filters from datapipes */ mdy_datapipe_quit(); /* Close files */ mce_close_output(&mdy_brightness_level_output); mce_close_output(&mdy_high_brightness_mode_output); /* Free strings */ g_free((void*)mdy_brightness_level_output.path); g_free(mdy_brightness_level_maximum_path); g_free(mdy_cabc_mode_file); g_free(mdy_cabc_available_modes_file); g_free((void*)mdy_brightness_hw_fading_output.path); g_free((void*)mdy_high_brightness_mode_output.path); g_free(mdy_low_power_mode_file); /* Remove all timer sources */ mdy_blanking_stop_pause_period(); mdy_brightness_stop_fade_timer(); mdy_blanking_cancel_dim(); mdy_blanking_unprime_adaptive_dimming(); mdy_blanking_cancel_off(); mdy_callstate_clear_changed(); mdy_blanking_inhibit_cancel_broadcast(); mdy_brightness_cancel_retry(); /* Stopping compositor ipc state machine can cause * scheduling of display state machine wakeups, so * it needs to be done 1st */ mdy_compositor_quit(); /* Cancel pending state machine updates */ mdy_stm_cancel_rethink(); mdy_poweron_led_rethink_cancel(); mdy_lpm_cancel_sanitize(); /* Remove callbacks on module unload */ mce_sensorfw_orient_set_notify(0); /* If we are shutting down/rebooting and we have fbdev * open, create a detached child process to hold on to * it so that display does not power off after mce & ui * side have been terminated */ if( mdy_shutdown_in_progress() && mce_fbdev_is_open() ) { /* Calculate when to release fbdev file descriptor * * use shutdown_started + 6.0 s */ int delay_ms = (int)(mdy_shutdown_started_tick + 6000 - mce_lib_get_boot_tick()); mce_fbdev_linger_after_exit(delay_ms); } /* Do not leave pending dbus calls behind */ mdy_topmost_window_forget_pid_query(); common_on_proximity_cancel(MODULE_NAME, 0, 0); compositor_stm_quit_config(); return; }