#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(void); static E_Config_Randr_Output *_e_randr_config_output_find(E_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 void _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) { printf("RR: apply out %p... [%s]\n", output, output->name); if ((output->cfg) && (!output->cfg->connect)) { printf("RR: output disabled\n"); _e_randr_output_active_set(output, EINA_FALSE); } else if ((output->cfg) && (output->status == ECORE_X_RANDR_CONNECTION_STATUS_CONNECTED)) { if (output->cfg) { printf("RR: output enabled [%i %i %ix%i | %2.3f orient %i]\n", output->cfg->geo.x, output->cfg->geo.y, output->cfg->geo.w, output->cfg->geo.h, output->cfg->refresh_rate, output->cfg->orient); printf("RR: update mode\n"); _e_randr_output_mode_update(output); printf("RR: active set...\n"); _e_randr_output_active_set(output, EINA_TRUE); printf("RR: active set done\n"); } else printf("RR: no cfg\n"); } else { printf("RR: ???\n"); } } printf("RR: ... lid update\n"); /* update lid status */ _e_randr_lid_update(); printf("RR: randr apply...\n"); /* apply randr settings */ _e_randr_apply(); printf("RR: update primary...\n"); /* 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, name, STR); E_CONFIG_VAL(D, T, edid, STR); 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); // TODO: primary must be name + edid 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->edid); 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; } static char * _e_randr_output_edid_string_get(Ecore_X_Window root, Ecore_X_Randr_Output output) { unsigned char *edid = NULL; unsigned long edid_len = 0; char *edid_str = NULL; edid = ecore_x_randr_output_edid_get(root, output, &edid_len); if (edid) { unsigned int k, kk; edid_str = malloc((edid_len * 2) + 1); if (edid_str) { const char *hexch = "0123456789abcdef"; for (kk = 0, k = 0; k < edid_len; k++) { edid_str[kk ] = hexch[(edid[k] >> 4) & 0xf]; edid_str[kk + 1] = hexch[ edid[k] & 0xf]; kk += 2; } edid_str[kk] = 0; } free(edid); } return edid_str; } /* 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); } printf("RR: crtc found %x | %i %i %ix%i - %x . %i\n", crtc->xid, crtc->geo.x, crtc->geo.y, crtc->geo.w, crtc->geo.h, crtc->mode, crtc->orient); } free(crtcs); } printf("RR: ====================\n"); /* 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_Randr_Output *output = NULL; Eina_Bool unknown = EINA_FALSE; output = E_NEW(E_Randr_Output, 1); if (!output) continue; e_randr->outputs = eina_list_append(e_randr->outputs, output); output->xid = outputs[j]; output->name = _e_randr_output_name_get(root, output->xid); output->is_lid = _e_randr_is_lid(output); output->edid = _e_randr_output_edid_string_get(root, outputs[j]); output->status = ecore_x_randr_output_connection_status_get(root, output->xid); output->cfg = _e_randr_config_output_find(output); if (!output->cfg) { output->cfg = _e_randr_config_output_new(); if (output->name) output->cfg->name = strdup(output->name); if (output->edid) output->cfg->edid = strdup(output->edid); unknown = EINA_TRUE; } printf("RR: output %x %s %i %p\n", output->xid, output->name, output->status, output->cfg); /* find a crtc if we want this output connected */ if (output->cfg->connect && (output->status == ECORE_X_RANDR_CONNECTION_STATUS_CONNECTED)) { _e_randr_output_active_set(output, EINA_TRUE); if (output->crtc) { printf("RR: ouput on crtc = %p\n", output->crtc); /* get orientation from crtc if not set */ if (!output->cfg->orient) output->cfg->orient = output->crtc->orient; /* find mode for output */ _e_randr_output_mode_update(output); /* set position from crtc if unknown */ if (unknown) { output->cfg->geo.x = output->crtc->geo.x; output->cfg->geo.y = output->crtc->geo.y; } } } } free(outputs); } /* 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); printf("RRR0: size range: %ix%i -> %ix%i\n", minw, minh, maxw, maxh); nx = maxw; ny = maxh; nw = nh = 0; EINA_LIST_FOREACH(e_randr->crtcs, l, crtc) { int x = 0, y = 0, w = 0, h = 0; Ecore_X_Randr_Orientation orient = ECORE_X_RANDR_ORIENTATION_ROT_0; Eina_Rectangle rect; printf("RRR0: crtc: %x %i %i %ix%i rot: %i mode: %i\n", crtc->xid, crtc->geo.x, crtc->geo.y, crtc->geo.w, crtc->geo.h, crtc->orient, crtc->mode); if (!crtc->outputs) continue; _e_randr_crtc_from_outputs_set(crtc); if (crtc->mode == 0) continue; x = crtc->geo.x; y = crtc->geo.y; orient = crtc->orient; rect = crtc->geo; _e_randr_config_mode_geometry(orient, &rect); w = rect.w; h = rect.h; if (((x + w) > maxw) || ((y + h) > maxh)) continue; if ((x + w) > nw) nw = x + w; if ((y + h) > nh) nh = y + h; if (x < nx) nx = x; if (y < ny) ny = y; } /* Adjust size according to origo */ nw -= nx; nh -= ny; /* apply the new screen size */ printf("RRR1: current screen size: %ix%i\n", nw, nh); ecore_x_randr_screen_current_size_set(root, nw, nh, -1, -1); nx = ny = nw = nh = 0; /* 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; printf("RRR2: crtc: %x %i %i %ix%i rot: %i mode: %i\n", crtc->xid, crtc->geo.x, crtc->geo.y, crtc->geo.w, crtc->geo.h, crtc->orient, crtc->mode); if (!crtc->outputs) { printf("RRR2: no outputs - off\n"); 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)) { printf("RRR2: crtc dose not fit - off\n"); 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) { printf("RRR2: cannot alloc coutputs\n"); continue; } EINA_LIST_FOREACH(crtc->outputs, ll, output) { /* TODO: If this condition isn't true, the output should not be in crtc->outputs. * crtc->outputs should only contain valid outputs */ if ((output->cfg) && (output->crtc == crtc) && (output->mode) && (output->status == ECORE_X_RANDR_CONNECTION_STATUS_CONNECTED)) { int i; Eina_Bool ok; ok = EINA_TRUE; /* TODO: This is wrong, one output shouldn't be twice in crtc list */ for (i = 0; i < count; i++) { if (coutputs[i] == output->xid) { ok = EINA_FALSE; printf("RRR: Error, same output twice on crtc."); printf(" output: '%s' lid: %i active: %i status: %i\n", output->name, output->is_lid, output->active, output->status); break; } } if (ok) { printf("RRR2: add output %s\n", output->name); coutputs[count] = output->xid; count++; } } } printf("RRR2: set mode %x | %i %i %ix%i | %x | %i\n", crtc->xid, crtc->geo.x, crtc->geo.y, crtc->geo.w, crtc->geo.h, crtc->mode, crtc->orient); /* apply our stored crtc settings */ if (count > 0) ecore_x_randr_crtc_settings_set(root, crtc->xid, coutputs, count, crtc->geo.x, crtc->geo.y, crtc->mode, crtc->orient); else { printf("RRR2: no coutputs - off\n"); ecore_x_randr_crtc_settings_set(root, crtc->xid, NULL, 0, 0, 0, 0, ECORE_X_RANDR_ORIENTATION_ROT_0); } /* cleanup */ free(coutputs); } /* 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) { Ecore_X_Window root; root = ecore_x_window_root_first_get(); output = E_NEW(E_Randr_Output, 1); if (!output) goto error; e_randr->outputs = eina_list_append(e_randr->outputs, output); output->xid = ev->output; output->name = _e_randr_output_name_get(root, output->xid); output->is_lid = _e_randr_is_lid(output); output->edid = _e_randr_output_edid_string_get(root, ev->output); changed = EINA_TRUE; output->cfg = _e_randr_config_output_find(output); if (!output->cfg) { output->cfg = _e_randr_config_output_new(); if (output->name) output->cfg->name = strdup(output->name); if (output->edid) output->cfg->edid = strdup(output->edid); } } 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) { Eina_Bool unknown = EINA_FALSE; if ((!output->crtc) || (output->crtc->xid == 0)) unknown = EINA_TRUE; /* connected */ if ((ev->crtc != 0) && ((!unknown) && (output->crtc->xid != ev->crtc))) { /* remove from old crtc */ _e_randr_output_active_set(output, EINA_FALSE); } if ((!output->active) && (output->cfg->connect)) { _e_randr_output_active_set(output, EINA_TRUE); if (output->crtc) { /* connect to crtc */ /* get orientation from crtc if not set */ if (!output->cfg->orient) output->cfg->orient = output->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(); printf("RR: ... 1 - %s\n", output->name); /* get mode infos */ mode_infos = ecore_x_randr_modes_info_get(root, &nmode_infos); if (nmode_infos == 0) goto error; printf("RR: ... 2\n"); /* get the list of modes for this output */ modes = ecore_x_randr_output_modes_get(root, output->xid, &nmodes, &pref); if (nmodes == 0) goto error; printf("RR: ... 3\n"); /* try to find the matching mode */ output->mode = 0; if ((output->cfg->geo.w != 0) && (output->cfg->geo.h != 0)) { printf("RR: ... 4\n"); for (i = 0; i < nmode_infos; i++) { double rate = 0.0; rate = e_randr_mode_refresh_rate_get(mode_infos[i]); printf("RR: ... 4.1 ... %i %1.3f cfg=%p modes=%p nmodes=%i\n", i, rate, output->cfg, modes, nmodes); 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))) { printf("RR: ... 4.2 - match %ix%i @ %2.3f\n", output->cfg->geo.w, output->cfg->geo.h, rate); output->mode = mode_infos[i]->xid; break; } printf("RR: ... 4.3\n"); } } printf("RR: ... 5\n"); /* see if we can use the mode of the crtc */ if ((!output->mode) && (output->crtc)) { E_Randr_Crtc *crtc; crtc = _e_randr_crtc_find(ecore_x_randr_output_crtc_get(root, output->xid)); printf("RR: ... 6\n"); if (crtc && crtc->mode) { printf("RR: ... 6.1\n"); 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 */ } } printf("RR: ... 7\n"); /* set preferred mode */ if (!output->mode) { printf("RR: ... 8\n"); 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++) { printf("RR: ... 9\n"); 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]); printf("RR: ... 9.1 %ix%i @ %2.3f\n", output->cfg->geo.w, output->cfg->geo.h, output->cfg->refresh_rate); break; } } printf("RR: ... 10\n"); 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(void) { E_Config_Randr_Output *cfg = NULL; if ((cfg = E_NEW(E_Config_Randr_Output, 1))) { /* 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(E_Randr_Output *output) { Eina_List *l; E_Config_Randr_Output *output_cfg; char b1[4096], b2[4096]; /* TODO: We should not match outputs with unknown name and edid */ snprintf(b1, sizeof(b1), "%s.%s", output->name ? output->name : "???", output->edid ? output->edid : "???"); EINA_LIST_FOREACH(e_randr_cfg->outputs, l, output_cfg) { snprintf(b2, sizeof(b2), "%s.%s", output_cfg->name ? output_cfg->name : "???", output_cfg->edid ? output_cfg->edid : "???"); if (!strcmp(b1, b2)) 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->xid == xid) return output; } return NULL; } static void _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; Eina_List *l; 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(ecore_x_randr_output_crtc_get(root, output->xid)))) { if (!crtc->outputs) goto done; } /* get a list of possible crtcs for this output */ possible = ecore_x_randr_output_possible_crtcs_get(root, output->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; } } /* get the list of modes for this output */ modes = ecore_x_randr_output_modes_get(root, output->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; } } /* last resort, just loop crtcs to find available */ EINA_LIST_FOREACH(e_randr->crtcs, l, crtc) { if (!crtc->outputs) goto done; crtc = NULL; } error: free(possible); free(modes); output->crtc = NULL; return; done: free(possible); free(modes); output->crtc = crtc; } 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->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->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; else if (strstr(cfg->name, "eDP")) ret = 1; else if (strstr(cfg->name, "edp")) ret = 1; else if (strstr(cfg->name, "EDP")) 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) { if (!output->active) continue; /* TODO: If status != connected, active should not be set */ if (output->status != ECORE_X_RANDR_CONNECTION_STATUS_CONNECTED) { printf("RRR: Error, unconnected output which is active."); printf(" output: '%s' lid: %i active: %i status: %i\n", output->name, output->is_lid, output->active, output->status); continue; } printf("RRR: output: '%s' lid: %i active: %i status: %i\n", output->name, output->is_lid, output->active, output->status); /* 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; int active_nonlid = 0; changed = EINA_FALSE; /* Find all non-lids */ EINA_LIST_FOREACH(e_randr->outputs, l, output) { if (output->is_lid) continue; if (!output->active) continue; active_nonlid++; } if (_e_randr_lid_is_closed && (active_nonlid > 0)) { /* Disable lids if closed and we have other monitors */ EINA_LIST_FOREACH(e_randr->outputs, l, output) { Eina_Bool active; if (!output->is_lid) continue; active = output->active; _e_randr_output_active_set(output, EINA_FALSE); if (active != output->active) changed = EINA_TRUE; } } else { /* Enable lids */ EINA_LIST_FOREACH(e_randr->outputs, l, output) { Eina_Bool active; if (!output->is_lid) continue; if (output->cfg && output->cfg->connect) { active = output->active; _e_randr_output_active_set(output, EINA_TRUE); if (active != output->active) 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; Eina_List *l; printf("RR: _e_randr_output_active_set... [%s] %i %i\n", output->name, output->active, active); output->active = EINA_FALSE; /* Remove output from all crtcs */ /* TODO: In theory the output should be only on one crtc, but to be sure... * And we always do this be sure we have a good crtc */ EINA_LIST_FOREACH(e_randr->crtcs, l, crtc) { crtc->outputs = eina_list_remove(crtc->outputs, output); } output->crtc = NULL; if (active) { /* Find a crtc for the output */ _e_randr_output_crtc_find(output); if (output->crtc) { printf("RR: ... found crtc %i\n", active); output->crtc->outputs = eina_list_append(output->crtc->outputs, output); output->active = EINA_TRUE; } } printf("RR: _e_randr_output_active_set... done - %p\n", output->crtc); /* Track all active outputs */ e_randr->active = 0; EINA_LIST_FOREACH(e_randr->outputs, l, output) { if (output->active) e_randr->active++; } } 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; }