#include "e.h" /* TODO: Handle orientation in stored config */ /* TODO: Ignore xrandr events triggered by changes in acpi cb */ /* TODO: Clone mode */ /* TODO: Lid at (x, y) == (0, 0) always */ /* TODO: Implement left-of, right-of, above and below placement relative for outputs */ /* local function prototypes */ static Eina_Bool _e_randr_config_load(void); static void _e_randr_config_new(void); static void _e_randr_config_free(void); static void _e_randr_free(void); static Eina_Bool _e_randr_config_cb_timer(void *data); static void _e_randr_load(void); static void _e_randr_apply(void); static void _e_randr_output_mode_update(E_Randr_Output *cfg); static E_Config_Randr_Output *_e_randr_config_output_new(Ecore_X_Window root, unsigned int id); static E_Config_Randr_Output *_e_randr_config_output_find(Ecore_X_Randr_Output output); static E_Randr_Crtc *_e_randr_crtc_find(Ecore_X_Randr_Crtc xid); static E_Randr_Output *_e_randr_output_find(Ecore_X_Randr_Output xid); static E_Randr_Crtc *_e_randr_output_crtc_find(E_Randr_Output *output); static void _e_randr_config_mode_geometry(Ecore_X_Randr_Orientation orient, Eina_Rectangle *rect); static void _e_randr_config_primary_update(void); static Eina_Bool _e_randr_event_cb_screen_change(void *data, int type, void *event); static Eina_Bool _e_randr_event_cb_crtc_change(void *data, int type, void *event); static Eina_Bool _e_randr_event_cb_output_change(void *data, int type, void *event); static void _e_randr_acpi_handler_add(void *data); static int _e_randr_is_lid(E_Randr_Output *cfg); static void _e_randr_crtc_from_outputs_set(E_Randr_Crtc *crtc); static Eina_Bool _e_randr_lid_update(void); static Eina_Bool _e_randr_output_mode_valid(Ecore_X_Randr_Mode mode, Ecore_X_Randr_Mode *modes, int nmodes); static void _e_randr_output_active_set(E_Randr_Output *output, Eina_Bool connected); static int _e_randr_config_output_cmp(const void *a, const void *b); static char *_e_randr_output_name_get(Ecore_X_Window root, Ecore_X_Randr_Output output); /* local variables */ static Eina_List *_randr_event_handlers = NULL; static E_Config_DD *_e_randr_edd = NULL; static E_Config_DD *_e_randr_output_edd = NULL; static int _e_randr_lid_is_closed = 0; /* external variables */ EAPI E_Config_Randr *e_randr_cfg = NULL; EAPI E_Randr *e_randr = NULL; /* private internal functions */ EINTERN Eina_Bool e_randr_init(void) { /* check if randr is available */ if (!ecore_x_randr_query()) return EINA_FALSE; /* get initial lid status */ _e_randr_lid_is_closed = (e_acpi_lid_status_get() == E_ACPI_LID_CLOSED); /* try to load config */ if (!_e_randr_config_load()) { /* NB: We should probably print an error here */ return EINA_FALSE; } /* tell randr that we are interested in receiving events * * NB: Requires RandR >= 1.2 */ if (ecore_x_randr_version_get() >= E_RANDR_VERSION_1_2) { Ecore_X_Window root = 0; if ((root = ecore_x_window_root_first_get())) ecore_x_randr_events_select(root, EINA_TRUE); /* setup randr event listeners */ E_LIST_HANDLER_APPEND(_randr_event_handlers, ECORE_X_EVENT_SCREEN_CHANGE, _e_randr_event_cb_screen_change, NULL); E_LIST_HANDLER_APPEND(_randr_event_handlers, ECORE_X_EVENT_RANDR_CRTC_CHANGE, _e_randr_event_cb_crtc_change, NULL); E_LIST_HANDLER_APPEND(_randr_event_handlers, ECORE_X_EVENT_RANDR_OUTPUT_CHANGE, _e_randr_event_cb_output_change, NULL); } /* delay setting up acpi handler, as acpi is init'ed after randr */ ecore_job_add(_e_randr_acpi_handler_add, NULL); return EINA_TRUE; } EINTERN int e_randr_shutdown(void) { /* check if randr is available */ if (!ecore_x_randr_query()) return 1; if (ecore_x_randr_version_get() >= E_RANDR_VERSION_1_2) { Ecore_X_Window root = 0; /* tell randr that we are not interested in receiving events anymore */ if ((root = ecore_x_window_root_first_get())) ecore_x_randr_events_select(root, EINA_FALSE); } /* remove event listeners */ E_FREE_LIST(_randr_event_handlers, ecore_event_handler_del); /* free config */ _e_randr_free(); _e_randr_config_free(); /* free edd */ E_CONFIG_DD_FREE(_e_randr_output_edd); E_CONFIG_DD_FREE(_e_randr_edd); return 1; } /* public API functions */ EAPI Eina_Bool e_randr_config_save(void) { /* save the new config */ return e_config_domain_save("e_randr", _e_randr_edd, e_randr_cfg); } EAPI void e_randr_config_apply(void) { Eina_List *l; E_Randr_Output *output; /* Update output mode */ EINA_LIST_FOREACH(e_randr->outputs, l, output) { if (!output->cfg->connect) _e_randr_output_active_set(output, EINA_FALSE); else if ((!output->active) && (output->status == ECORE_X_RANDR_CONNECTION_STATUS_CONNECTED)) { if (_e_randr_output_crtc_find(output)) { _e_randr_output_mode_update(output); _e_randr_output_active_set(output, EINA_TRUE); } } } /* update lid status */ _e_randr_lid_update(); /* apply randr settings */ _e_randr_apply(); /* update primary output */ _e_randr_config_primary_update(); } EAPI double e_randr_mode_refresh_rate_get(Ecore_X_Randr_Mode_Info *mode) { double rate = 0.0; if (mode) { if ((mode->hTotal) && (mode->vTotal)) rate = ((double)mode->dotClock / ((double)mode->hTotal * (double)mode->vTotal)); rate = round(rate); } return rate; } /* local functions */ static Eina_Bool _e_randr_config_load(void) { Eina_Bool do_restore = EINA_TRUE; /* define edd for output config */ _e_randr_output_edd = E_CONFIG_DD_NEW("E_Config_Randr_Output", E_Config_Randr_Output); #undef T #undef D #define T E_Config_Randr_Output #define D _e_randr_output_edd E_CONFIG_VAL(D, T, xid, UINT); E_CONFIG_VAL(D, T, crtc, UINT); E_CONFIG_VAL(D, T, orient, UINT); E_CONFIG_VAL(D, T, geo.x, INT); E_CONFIG_VAL(D, T, geo.y, INT); E_CONFIG_VAL(D, T, geo.w, INT); E_CONFIG_VAL(D, T, geo.h, INT); E_CONFIG_VAL(D, T, refresh_rate, DOUBLE); E_CONFIG_VAL(D, T, connect, UCHAR); /* define edd for randr config */ _e_randr_edd = E_CONFIG_DD_NEW("E_Config_Randr", E_Config_Randr); #undef T #undef D #define T E_Config_Randr #define D _e_randr_edd E_CONFIG_VAL(D, T, version, INT); E_CONFIG_LIST(D, T, outputs, _e_randr_output_edd); E_CONFIG_VAL(D, T, restore, UCHAR); E_CONFIG_VAL(D, T, config_timestamp, ULL); E_CONFIG_VAL(D, T, primary, UINT); /* try to load the randr config */ if ((e_randr_cfg = e_config_domain_load("e_randr", _e_randr_edd))) { /* check randr config version */ if (e_randr_cfg->version < (E_RANDR_CONFIG_FILE_EPOCH * 1000000)) { /* config is too old */ do_restore = EINA_FALSE; _e_randr_config_free(); ecore_timer_add(1.0, _e_randr_config_cb_timer, _("Settings data needed upgrading. Your old settings have
" "been wiped and a new set of defaults initialized. This
" "will happen regularly during development, so don't report a
" "bug. This simply means Enlightenment needs new settings
" "data by default for usable functionality that your old
" "settings simply lack. This new set of defaults will fix
" "that by adding it in. You can re-configure things now to your
" "liking. Sorry for the hiccup in your settings.
")); } else if (e_randr_cfg->version > E_RANDR_CONFIG_FILE_VERSION) { /* config is too new */ do_restore = EINA_FALSE; _e_randr_config_free(); ecore_timer_add(1.0, _e_randr_config_cb_timer, _("Your settings are NEWER than Enlightenment. This is very
" "strange. This should not happen unless you downgraded
" "Enlightenment or copied the settings from a place where
" "a newer version of Enlightenment was running. This is bad and
" "as a precaution your settings have been now restored to
" "defaults. Sorry for the inconvenience.
")); } } /* if config was too old or too new, then reload a fresh one */ if (!e_randr_cfg) { _e_randr_config_new(); do_restore = EINA_FALSE; } else { #define CONFIG_VERSION_CHECK(VERSION) \ if (e_randr_cfg->version - (E_RANDR_CONFIG_FILE_EPOCH * 1000000) < (VERSION)) CONFIG_VERSION_CHECK(4) { E_Config_Randr_Output *output; Eina_List *l; /* Set refresh_rate to 60 */ EINA_LIST_FOREACH(e_randr_cfg->outputs, l, output) if (dblequal(output->refresh_rate, 0.0)) output->refresh_rate = 60.0; } } if (!e_randr_cfg) return EINA_FALSE; _e_randr_load(); e_randr_config_save(); if ((do_restore) && (e_randr_cfg->restore)) _e_randr_apply(); return EINA_TRUE; } static void _e_randr_config_new(void) { Ecore_X_Window root = 0; /* create new randr cfg */ if (!(e_randr_cfg = E_NEW(E_Config_Randr, 1))) return; /* grab the root window once */ root = ecore_x_window_root_first_get(); /* set version */ e_randr_cfg->version = E_RANDR_CONFIG_FILE_VERSION; /* by default, restore config */ e_randr_cfg->restore = EINA_TRUE; /* remember current primary */ e_randr_cfg->primary = ecore_x_randr_primary_output_get(root); /* update recorded config timestamp */ e_randr_cfg->config_timestamp = ecore_x_randr_config_timestamp_get(root); } static void _e_randr_config_free(void) { E_Config_Randr_Output *output = NULL; /* safety check */ if (!e_randr_cfg) return; /* loop the config outputs and free them */ EINA_LIST_FREE(e_randr_cfg->outputs, output) { E_FREE(output); } /* free the config */ E_FREE(e_randr_cfg); } static void _e_randr_free(void) { E_Randr_Crtc *crtc = NULL; E_Randr_Output *output = NULL; /* safety check */ if (!e_randr) return; /* loop the crtcs and free them */ EINA_LIST_FREE(e_randr->crtcs, crtc) { free(crtc); } /* loop the outputs and free them */ EINA_LIST_FREE(e_randr->outputs, output) { free(output->name); free(output); } /* free the config */ E_FREE(e_randr); } static Eina_Bool _e_randr_config_cb_timer(void *data) { e_util_dialog_show(_("Randr Settings Upgraded"), "%s", (char *)data); return EINA_FALSE; } /* function to map X's settings with E's settings */ static void _e_randr_load(void) { Ecore_X_Window root = 0; Ecore_X_Randr_Output *outputs = NULL; int noutputs = 0; Ecore_X_Randr_Crtc *crtcs = NULL; int ncrtcs = 0, i = 0; e_randr = E_NEW(E_Randr, 1); if (!e_randr) return; /* grab the root window once */ root = ecore_x_window_root_first_get(); /* try to get the list of crtcs from x */ if ((crtcs = ecore_x_randr_crtcs_get(root, &ncrtcs))) { /* loop the crtcs */ for (i = 0; i < ncrtcs; i++) { E_Randr_Crtc *crtc; Ecore_X_Randr_Crtc_Info *cinfo; crtc = E_NEW(E_Randr_Crtc, 1); if (!crtc) continue; e_randr->crtcs = eina_list_append(e_randr->crtcs, crtc); crtc->xid = crtcs[i]; /* get crtc info from X */ if ((cinfo = ecore_x_randr_crtc_info_get(root, crtc->xid))) { crtc->geo.x = cinfo->x; crtc->geo.y = cinfo->y; crtc->geo.w = cinfo->width; crtc->geo.h = cinfo->height; crtc->mode = cinfo->mode; crtc->orient = cinfo->rotation; ecore_x_randr_crtc_info_free(cinfo); } } free(crtcs); } /* try to get the list of outputs from x */ if ((outputs = ecore_x_randr_outputs_get(root, &noutputs))) { int j = 0; for (j = 0; j < noutputs; j++) { E_Config_Randr_Output *output_cfg = NULL; E_Randr_Output *output = NULL; Eina_Bool unknown = EINA_FALSE; output_cfg = _e_randr_config_output_find(outputs[j]); if (!output_cfg) { output_cfg = _e_randr_config_output_new(root, outputs[j]); unknown = EINA_TRUE; } if (!output_cfg) continue; output = E_NEW(E_Randr_Output, 1); if (!output) continue; e_randr->outputs = eina_list_append(e_randr->outputs, output); output->cfg = output_cfg; output->name = _e_randr_output_name_get(root, output->cfg->xid); output->is_lid = _e_randr_is_lid(output); output->status = ecore_x_randr_output_connection_status_get(root, output->cfg->xid); /* find a crtc if we want this output connected */ if (output->cfg->connect && (output->status == ECORE_X_RANDR_CONNECTION_STATUS_CONNECTED)) { E_Randr_Crtc *crtc; crtc = _e_randr_output_crtc_find(output); if (crtc) { _e_randr_output_active_set(output, EINA_TRUE); /* get orientation from crtc if not set */ if (!output->cfg->orient) output->cfg->orient = crtc->orient; /* find mode for output */ _e_randr_output_mode_update(output); /* set position from crtc if unknown */ if (unknown) { output->cfg->geo.x = crtc->geo.x; output->cfg->geo.y = crtc->geo.y; } } } } free(outputs); } /* sort list by output id */ e_randr_cfg->outputs = eina_list_sort(e_randr_cfg->outputs, -1, _e_randr_config_output_cmp); /* update lid status */ _e_randr_lid_update(); /* update primary output */ _e_randr_config_primary_update(); /* update recorded config timestamp */ e_randr_cfg->config_timestamp = ecore_x_randr_config_timestamp_get(root); /* save the config */ e_randr_config_save(); } static void _e_randr_apply(void) { E_Randr_Crtc *crtc; Eina_List *l; Ecore_X_Window root = 0; int nw = 0, nh = 0; int minw = 0, minh = 0; int maxw = 0, maxh = 0; int nx = INT_MAX, ny = INT_MAX; /* don't try to restore if we have fake screens */ if (e_xinerama_fake_screens_exist()) return; /* grab the X server so that we can apply settings without triggering * any randr event updates until we are done */ ecore_x_grab(); /* get the root window */ root = ecore_x_window_root_first_get(); /* get the min and max screen size */ ecore_x_randr_screen_size_range_get(root, &minw, &minh, &maxw, &maxh); /* loop our lists of crtcs */ EINA_LIST_FOREACH(e_randr->crtcs, l, crtc) { E_Randr_Output *output; Eina_List *ll; int x = 0, y = 0, w = 0, h = 0; Ecore_X_Randr_Mode mode = 0; Ecore_X_Randr_Orientation orient = ECORE_X_RANDR_ORIENTATION_ROT_0; Eina_Rectangle rect; int count = 0; Ecore_X_Randr_Output *coutputs; /* disable crtc if no outputs */ if (!crtc->outputs) { ecore_x_randr_crtc_settings_set(root, crtc->xid, NULL, 0, 0, 0, 0, ECORE_X_RANDR_ORIENTATION_ROT_0); continue; } /* set config from connected outputs */ _e_randr_crtc_from_outputs_set(crtc); x = crtc->geo.x; y = crtc->geo.y; mode = crtc->mode; orient = crtc->orient; /* at this point, we should have geometry, mode and orientation. * we can now proceed to calculate crtc size */ rect = crtc->geo; _e_randr_config_mode_geometry(orient, &rect); w = rect.w; h = rect.h; /* if the crtc does not fit, disable it */ if (((x + w) > maxw) || ((y + h) > maxh) || (mode == 0)) { ecore_x_randr_crtc_settings_set(root, crtc->xid, NULL, 0, 0, 0, 0, ECORE_X_RANDR_ORIENTATION_ROT_0); continue; } /* find outputs to enable on crtc */ coutputs = calloc(eina_list_count(crtc->outputs), sizeof(Ecore_X_Randr_Output)); if (!coutputs) { /* TODO: ERROR! */ continue; } EINA_LIST_FOREACH(crtc->outputs, ll, output) { coutputs[count] = output->cfg->xid; count++; } /* get new size */ if ((x + w) > nw) nw = x + w; if ((y + h) > nh) nh = y + h; if (x < nx) nx = x; if (y < ny) ny = y; /* apply our stored crtc settings */ ecore_x_randr_crtc_settings_set(root, crtc->xid, coutputs, count, crtc->geo.x, crtc->geo.y, crtc->mode, crtc->orient); /* cleanup */ free(coutputs); } /* Adjust size according to origo */ nw -= nx; nh -= ny; /* apply the new screen size */ ecore_x_randr_screen_current_size_set(root, nw, nh, -1, -1); /* this moves screen to (0, 0) */ ecore_x_randr_screen_reset(root); /* release the server grab */ ecore_x_ungrab(); } static Eina_Bool _e_randr_event_cb_screen_change(void *data EINA_UNUSED, int type EINA_UNUSED, void *event) { Ecore_X_Event_Screen_Change *ev; Eina_Bool changed = EINA_FALSE; Ecore_X_Randr_Output primary = 0; ev = event; /* check if this event's root window is Our root window */ if (ev->root != e_manager_current_get()->root) return ECORE_CALLBACK_RENEW; primary = ecore_x_randr_primary_output_get(ev->root); if (e_randr_cfg->primary != primary) { e_randr_cfg->primary = primary; changed = EINA_TRUE; } if (e_randr_cfg->config_timestamp != ev->config_time) { e_randr_cfg->config_timestamp = ev->config_time; changed = EINA_TRUE; } if (changed) e_randr_config_save(); return ECORE_CALLBACK_RENEW; } static Eina_Bool _e_randr_event_cb_crtc_change(void *data EINA_UNUSED, int type EINA_UNUSED, void *event) { Ecore_X_Event_Randr_Crtc_Change *ev; E_Randr_Crtc *crtc; ev = event; crtc = _e_randr_crtc_find(ev->crtc); if (!crtc) { crtc = E_NEW(E_Randr_Crtc, 1); if (crtc) { e_randr->crtcs = eina_list_append(e_randr->crtcs, crtc); crtc->xid = ev->crtc; crtc->geo = ev->geo; crtc->mode = ev->mode; crtc->orient = ev->orientation; } } else { /* check (and update if needed) our crtc config */ if ((ev->mode != crtc->mode) || (ev->orientation != crtc->orient) || (ev->geo.x != crtc->geo.x) || (ev->geo.y != crtc->geo.y) || (ev->geo.w != crtc->geo.w) || (ev->geo.h != crtc->geo.h)) { crtc->mode = ev->mode; crtc->orient = ev->orientation; crtc->geo = ev->geo; } } return ECORE_CALLBACK_RENEW; } static Eina_Bool _e_randr_event_cb_output_change(void *data EINA_UNUSED, int type EINA_UNUSED, void *event) { Ecore_X_Event_Randr_Output_Change *ev; E_Randr_Output *output; Eina_Bool changed = EINA_FALSE; ev = event; /* check if this event's root window is Our root window */ if (ev->win != e_manager_current_get()->root) return ECORE_CALLBACK_RENEW; /* loop our crtcs and try to find this output */ output = _e_randr_output_find(ev->output); if (!output) { E_Config_Randr_Output *output_cfg = NULL; Ecore_X_Window root; root = ecore_x_window_root_first_get(); output = E_NEW(E_Randr_Output, 1); if (!output) goto error; output_cfg = _e_randr_config_output_find(ev->output); if (!output_cfg) output_cfg = _e_randr_config_output_new(root, ev->output); if (!output_cfg) goto error; e_randr->outputs = eina_list_append(e_randr->outputs, output); output->cfg = output_cfg; output->name = _e_randr_output_name_get(root, output->cfg->xid); output->is_lid = _e_randr_is_lid(output); changed = EINA_TRUE; } output->status = ev->connection; /* we know this output */ if (output->is_lid && _e_randr_lid_is_closed) { /* ignore event from disconnected lid */ } else if (ev->connection == ECORE_X_RANDR_CONNECTION_STATUS_CONNECTED) { E_Randr_Crtc *crtc = NULL; Eina_Bool unknown = EINA_FALSE; if (output->cfg->crtc == 0) unknown = EINA_TRUE; /* connected */ if ((ev->crtc != 0) && (output->cfg->crtc != ev->crtc)) { /* remove from old crtc */ _e_randr_output_active_set(output, EINA_FALSE); /* set new crtc on output */ output->cfg->crtc = ev->crtc; } if ((!output->active) && (output->cfg->connect)) { crtc = _e_randr_output_crtc_find(output); if (crtc) { /* connect to crtc */ _e_randr_output_active_set(output, EINA_TRUE); /* get orientation from crtc if not set */ if (!output->cfg->orient) output->cfg->orient = crtc->orient; /* validate output mode */ _e_randr_output_mode_update(output); /* if unknown position at far right */ if (unknown) { E_Randr_Output *other; Eina_List *l; EINA_LIST_FOREACH(e_randr->outputs, l, other) { if ((other == output) || (!output->active)) continue; if ((other->cfg->geo.x + other->cfg->geo.w) > output->cfg->geo.x) output->cfg->geo.x = other->cfg->geo.x + other->cfg->geo.w; } } } changed = EINA_TRUE; } } else if (ev->connection == ECORE_X_RANDR_CONNECTION_STATUS_DISCONNECTED) { /* disconnected */ if (output->active) { _e_randr_output_active_set(output, EINA_FALSE); changed = EINA_TRUE; } } /* save the config if anything changed or we added a new one */ if (changed) { _e_randr_lid_update(); _e_randr_config_primary_update(); _e_randr_apply(); e_randr_config_save(); } return ECORE_CALLBACK_RENEW; error: free(output); return ECORE_CALLBACK_RENEW; } static Eina_Bool _e_randr_event_cb_acpi(void *data EINA_UNUSED, int type EINA_UNUSED, void *event) { E_Event_Acpi *ev; ev = event; if (ev->type == E_ACPI_TYPE_LID) { Eina_Bool changed; _e_randr_lid_is_closed = (ev->status == E_ACPI_LID_CLOSED); changed = _e_randr_lid_update(); if (changed) { /* force new evaluation of primary */ e_randr_cfg->primary = 0; _e_randr_config_primary_update(); _e_randr_apply(); } } return ECORE_CALLBACK_RENEW; } static void _e_randr_output_mode_update(E_Randr_Output *output) { Ecore_X_Window root = 0; Ecore_X_Randr_Mode *modes = NULL; int nmodes = 0, pref = 0; Ecore_X_Randr_Mode_Info **mode_infos = NULL; int nmode_infos = 0; int i = 0; /* grab the root window */ root = ecore_x_window_root_first_get(); /* get mode infos */ mode_infos = ecore_x_randr_modes_info_get(root, &nmode_infos); if (nmode_infos == 0) goto error; /* get the list of modes for this output */ modes = ecore_x_randr_output_modes_get(root, output->cfg->xid, &nmodes, &pref); if (nmodes == 0) goto error; /* try to find the matching mode */ output->mode = 0; if ((output->cfg->geo.w != 0) && (output->cfg->geo.h != 0)) { for (i = 0; i < nmode_infos; i++) { double rate = 0.0; rate = e_randr_mode_refresh_rate_get(mode_infos[i]); if ((mode_infos[i]->width == (unsigned int)output->cfg->geo.w) && (mode_infos[i]->height == (unsigned int)output->cfg->geo.h) && (dblequal(rate, output->cfg->refresh_rate)) && (_e_randr_output_mode_valid(mode_infos[i]->xid, modes, nmodes))) { output->mode = mode_infos[i]->xid; break; } } } /* see if we can use the mode of the crtc */ if (!output->mode) { E_Randr_Crtc *crtc; crtc = _e_randr_crtc_find(output->cfg->crtc); if (crtc && crtc->mode) { if (_e_randr_output_mode_valid(crtc->mode, modes, nmodes)) output->mode = crtc->mode; /* TODO: See if we have a mode of the same size with another mode id */ } } /* set preferred mode */ if (!output->mode) { if (pref > 0) output->mode = modes[pref - 1]; else output->mode = modes[0]; } /* set width and height from mode */ for (i = 0; i < nmode_infos; i++) { if (mode_infos[i]->xid == output->mode) { output->cfg->geo.w = mode_infos[i]->width; output->cfg->geo.h = mode_infos[i]->height; output->cfg->refresh_rate = e_randr_mode_refresh_rate_get(mode_infos[i]); break; } } if (modes) free(modes); if (mode_infos) { for (i = 0; i < nmode_infos; i++) free(mode_infos[i]); free(mode_infos); } return; error: if (modes) free(modes); if (mode_infos) { for (i = 0; i < nmode_infos; i++) free(mode_infos[i]); free(mode_infos); } output->cfg->geo.x = output->cfg->geo.y = output->cfg->geo.w = output->cfg->geo.h = 0; output->cfg->refresh_rate = 0.0; output->mode = 0; return; } static E_Config_Randr_Output * _e_randr_config_output_new(Ecore_X_Window root, unsigned int id) { E_Config_Randr_Output *cfg = NULL; if ((cfg = E_NEW(E_Config_Randr_Output, 1))) { /* assign output xid */ cfg->xid = id; /* get the crtc for this output */ cfg->crtc = ecore_x_randr_output_crtc_get(root, cfg->xid); /* all new outputs should connect automatically */ /* TODO: config option */ cfg->connect = EINA_TRUE; /* append this output config to randr config */ e_randr_cfg->outputs = eina_list_append(e_randr_cfg->outputs, cfg); } return cfg; } static E_Config_Randr_Output * _e_randr_config_output_find(Ecore_X_Randr_Output output) { Eina_List *l; E_Config_Randr_Output *output_cfg; EINA_LIST_FOREACH(e_randr_cfg->outputs, l, output_cfg) { if (output_cfg->xid == output) return output_cfg; } return NULL; } static E_Randr_Crtc * _e_randr_crtc_find(Ecore_X_Randr_Crtc xid) { Eina_List *l; E_Randr_Crtc *crtc; EINA_LIST_FOREACH(e_randr->crtcs, l, crtc) { if (crtc->xid == xid) return crtc; } return NULL; } static E_Randr_Output * _e_randr_output_find(Ecore_X_Randr_Output xid) { Eina_List *l; E_Randr_Output *output; EINA_LIST_FOREACH(e_randr->outputs, l, output) { if (output->cfg->xid == xid) return output; } return NULL; } static E_Randr_Crtc * _e_randr_output_crtc_find(E_Randr_Output *output) { Ecore_X_Window root = 0; E_Randr_Crtc *crtc = NULL; Ecore_X_Randr_Crtc *possible = NULL; Ecore_X_Randr_Mode *modes = NULL; int num = 0, i = 0; int nmodes, pref; /* grab the root window */ root = ecore_x_window_root_first_get(); /* check if last is available */ if ((crtc = _e_randr_crtc_find(output->cfg->crtc))) { if (!crtc->outputs) goto done; } crtc = NULL; /* get a list of possible crtcs for this output */ possible = ecore_x_randr_output_possible_crtcs_get(root, output->cfg->xid, &num); if (num == 0) goto error; /* loop the possible crtcs */ for (i = 0; i < num; i++) { if ((crtc = _e_randr_crtc_find(possible[i]))) { if (!crtc->outputs) goto done; } } crtc = NULL; /* get the list of modes for this output */ modes = ecore_x_randr_output_modes_get(root, output->cfg->xid, &nmodes, &pref); if (nmodes == 0) goto error; /* can we clone this output */ for (i = 0; i < num; i++) { if ((crtc = _e_randr_crtc_find(possible[i]))) { /* check if mode is valid for this output */ if (_e_randr_output_mode_valid(crtc->mode, modes, nmodes)) goto done; } } crtc = NULL; done: free(possible); free(modes); if (crtc) output->cfg->crtc = crtc->xid; return crtc; error: free(possible); free(modes); output->cfg->crtc = 0; return NULL; } static void _e_randr_config_mode_geometry(Ecore_X_Randr_Orientation orient, Eina_Rectangle *rect) { int mw = 0, mh = 0; /* based on orientation, calculate mode sizes */ switch (orient) { case ECORE_X_RANDR_ORIENTATION_ROT_0: case ECORE_X_RANDR_ORIENTATION_ROT_180: mw = rect->w; mh = rect->h; break; case ECORE_X_RANDR_ORIENTATION_ROT_90: case ECORE_X_RANDR_ORIENTATION_ROT_270: mw = rect->h; mh = rect->w; break; default: break; } rect->x = 0; rect->y = 0; rect->w = mw; rect->h = mh; } static void _e_randr_config_primary_update(void) { Ecore_X_Window root = 0; Eina_List *l; E_Randr_Output *output; Ecore_X_Randr_Output primary = 0; /* check if we agree with system setup */ root = ecore_x_window_root_first_get(); primary = ecore_x_randr_primary_output_get(root); /* see if our selected primary exists and is connected */ output = _e_randr_output_find(e_randr_cfg->primary); if ((!output) || (!output->active)) e_randr_cfg->primary = 0; /* check if system primary is the same as ours */ if ((primary > 0) && (primary == e_randr_cfg->primary)) return; if ((e_randr_cfg->primary > 0) && output && output->active) { /* prefer our primary */ ecore_x_randr_primary_output_set(root, e_randr_cfg->primary); } else { /* find lid */ e_randr_cfg->primary = 0; EINA_LIST_FOREACH(e_randr->outputs, l, output) { if (output->is_lid) { if (output->active) e_randr_cfg->primary = output->cfg->xid; break; } } if (!e_randr_cfg->primary) { /* TODO: prefer top-left active monitor */ /* no lid, use first existing */ EINA_LIST_FOREACH(e_randr->outputs, l, output) { if (output->active) { e_randr_cfg->primary = output->cfg->xid; break; } } } /* set primary */ ecore_x_randr_primary_output_set(root, e_randr_cfg->primary); } } static void _e_randr_acpi_handler_add(void *data EINA_UNUSED) { E_LIST_HANDLER_APPEND(_randr_event_handlers, E_EVENT_ACPI, _e_randr_event_cb_acpi, NULL); } static int _e_randr_is_lid(E_Randr_Output *cfg) { /* TODO: ecore_x_randr_output_connector_type_get */ int ret = 0; if (!cfg->name) return 0; if (strstr(cfg->name, "LVDS")) ret = 1; else if (strstr(cfg->name, "lvds")) ret = 1; else if (strstr(cfg->name, "Lvds")) ret = 1; else if (strstr(cfg->name, "LCD")) ret = 1; return ret; } static void _e_randr_crtc_from_outputs_set(E_Randr_Crtc *crtc) { E_Randr_Output *output; Eina_List *l; EINA_LIST_FOREACH(crtc->outputs, l, output) { /* TODO: Match all connected outputs, not only the first */ crtc->mode = output->mode; crtc->orient = output->cfg->orient; crtc->geo = output->cfg->geo; break; } } static Eina_Bool _e_randr_lid_update(void) { E_Randr_Output *output; Eina_List *l; Eina_Bool changed = EINA_FALSE; /* loop through connections to find lid */ changed = EINA_FALSE; EINA_LIST_FOREACH(e_randr->outputs, l, output) { if (!output->is_lid) continue; /* only disable lid if we got more than 1 connected output */ if ((_e_randr_lid_is_closed) && (output->active) && (e_randr->active > 1)) { _e_randr_output_active_set(output, EINA_FALSE); changed = EINA_TRUE; } else if (!output->active) { _e_randr_output_active_set(output, EINA_TRUE); changed = EINA_TRUE; } } return changed; } static Eina_Bool _e_randr_output_mode_valid(Ecore_X_Randr_Mode mode, Ecore_X_Randr_Mode *modes, int nmodes) { Eina_Bool valid = EINA_FALSE; int i; for (i = 0; i < nmodes; i++) { if (modes[i] == mode) { valid = EINA_TRUE; break; } } return valid; } static void _e_randr_output_active_set(E_Randr_Output *output, Eina_Bool active) { E_Randr_Crtc *crtc; if (output->active == active) return; output->active = active; crtc = _e_randr_crtc_find(output->cfg->crtc); if (crtc) { if (active) { crtc->outputs = eina_list_append(crtc->outputs, output); output->crtc = crtc; e_randr->active++; } else { crtc->outputs = eina_list_remove(crtc->outputs, output); output->crtc = NULL; e_randr->active--; } } } static int _e_randr_config_output_cmp(const void *a, const void *b) { const E_Config_Randr_Output *cfg1 = a; const E_Config_Randr_Output *cfg2 = b; if (cfg1->xid < cfg2->xid) return -1; if (cfg1->xid > cfg2->xid) return 1; return 0; } static char * _e_randr_output_name_get(Ecore_X_Window root, Ecore_X_Randr_Output output) { char *name; name = ecore_x_randr_output_name_get(root, output, NULL); if (!name) { unsigned char *edid = NULL; unsigned long edid_length = 0; /* get the edid for this output */ if ((edid = ecore_x_randr_output_edid_get(root, output, &edid_length))) { /* get output name */ name = ecore_x_randr_edid_display_name_get(edid, edid_length); /* free any memory allocated from ecore_x_randr */ free(edid); } } return name; }