diff options
author | Simon Rettberg | 2018-08-15 17:52:41 +0200 |
---|---|---|
committer | Simon Rettberg | 2018-08-15 17:52:41 +0200 |
commit | c46a9a1272f3b9265759c91ce2e1e6c7836fe74c (patch) | |
tree | 7477e9e15f7ef0782609975d811692db7a405b79 | |
parent | Fix conditional bring-to-top: Only when dropdown is visible, not control (diff) | |
download | beamergui-c46a9a1272f3b9265759c91ce2e1e6c7836fe74c.tar.gz beamergui-c46a9a1272f3b9265759c91ce2e1e6c7836fe74c.tar.xz beamergui-c46a9a1272f3b9265759c91ce2e1e6c7836fe74c.zip |
New Version, WIP
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/cvt.c | 552 | ||||
-rw-r--r-- | src/cvt.h | 26 | ||||
-rw-r--r-- | src/i18n/de.ts | 72 | ||||
-rw-r--r-- | src/main.cpp | 101 | ||||
-rw-r--r-- | src/main.h | 12 | ||||
-rw-r--r-- | src/widget.cpp | 588 | ||||
-rw-r--r-- | src/widget.h | 49 | ||||
-rw-r--r-- | src/widget.ui | 206 | ||||
-rw-r--r-- | src/x.cpp | 1062 | ||||
-rw-r--r-- | src/x.h | 337 |
11 files changed, 1748 insertions, 1259 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 65a4e72..448676c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,7 @@ endif() set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) -file(GLOB_RECURSE BEAMERGUI_SOURCES src/*.cpp) +file(GLOB_RECURSE BEAMERGUI_SOURCES src/*.cpp src/*.c) file(GLOB_RECURSE BEAMERGUI_UIS src/*.ui) file(GLOB_RECURSE BEAMERGUI_RESOURCES src/*.qrc) file(GLOB_RECURSE BEAMERGUI_TSS src/i18n/*.ts) diff --git a/src/cvt.c b/src/cvt.c new file mode 100644 index 0000000..9563f0f --- /dev/null +++ b/src/cvt.c @@ -0,0 +1,552 @@ +#include "cvt.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Based on the cvt util: + * http://www.uruk.org/projects/cvt/cvt.c + */ + +#include <stdlib.h> +#include <string.h> +#include <math.h> + +#define CLOCK_STEP 0.25 /* Clock steps in MHz */ +#define MARGIN_PERCENT 1.8 /* % of active vertical image */ +#define H_SYNC_PER 8.0 /* sync % of horizontal image */ +#define CELL_GRAN 8.4999 /* assumed character cell granularity */ +#define CELL_GRAN_RND 8.0 /* assumed character cell granularity (round)*/ +#define MIN_V_BPORCH 3.0 /* width of vsync in lines */ +#define MIN_V_PORCH_RND 3.0 /* width of vsync in lines */ +#define M 600.0 /* blanking formula gradient */ +#define C 40.0 /* blanking formula offset */ +#define K 128.0 /* blanking formula scaling factor */ +#define J 20.0 /* blanking formula scaling factor */ + +/* Standard Timing Parameters */ +#define MIN_VSYNC_BP 550.0 /* min time of vsync + back porch (us) */ +#define H_SYNC_PERCENT 8.0 /* width of hsync as % of total line */ + +/* Reduced Blanking defines */ +#define RB_MIN_V_BPORCH 6.0 /* lines */ +#define RB_V_FPORCH 3.0 /* lines */ +#define RB_MIN_V_BLANK 460.0 /* us */ +#define RB_H_SYNC 32.0 /* pixels */ +#define RB_H_BLANK 160.0 /* pixels */ + +/* C' and M' are part of the Blanking Duty Cycle computation */ + +#define C_PRIME (((C - J) * K/256.0) + J) +#define M_PRIME (K/256.0 * M) + +/* NOP out prints */ +#define print_value(...) (void)0 + +typedef struct __options +{ + int x, y; + int reduced_blank, interlaced; + int xf86mode, fbmode; + float v_freq; +} options; + + +/* + * vert_refresh() - as defined by the CVT Timing Standard, compute the + * Stage 1 Parameters using the vertical refresh frequency. In other + * words: input a desired resolution and desired refresh rate, and + * output the CVT mode timings. + * + * XXX margin computations are implemented but not tested (nor used by + * XFree86 of fbset mode descriptions, from what I can tell). + */ + +mode *vert_refresh (int h_pixels, int v_lines, float freq, + int interlaced, int reduced_blank, int margins) +{ + float h_pixels_rnd; + float v_lines_rnd; + float v_field_rate_rqd; + float top_margin; + float bot_margin; + float interlace; + float h_period_est; + float v_sync_bp; + float total_v_lines; + float left_margin; + float right_margin; + float total_active_pixels; + float ideal_duty_cycle; + float h_blank; + float total_pixels; + + float cur_duty_cycle; + float v_sync; + float v_sync_rnd, h_sync_rnd; + float h_back_porch, v_front_porch, h_front_porch; + + float vbi_lines, act_vbi_lines, rb_min_vbi; + float act_pixel_freq, act_h_freq; + float act_field_rate, act_frame_rate; + char *aspect_ratio; + int stage; + + mode *m = (mode*) malloc (sizeof (mode)); + + + /* 1. Required Field Rate + * + * This is slightly different from the spreadsheet because we use + * a different result for interlaced video modes. Simplifies this + * to the input field rate. + * + * [V FIELD RATE RQD] = [I/P FREQ RQD] + */ + + v_field_rate_rqd = freq; + + print_value(1, "[V FIELD RATE RQD]", v_field_rate_rqd); + + + /* 2. Horizontal Pixels + * + * In order to give correct results, the number of horizontal + * pixels requested is first processed to ensure that it is divisible + * by the character size, by rounding it to the nearest character + * cell boundary. + * + * [H PIXELS RND] = ((ROUNDDOWN([H PIXELS]/[CELL GRAN RND],0)) + * *[CELLGRAN RND]) + */ + + h_pixels_rnd = floor((float) h_pixels / CELL_GRAN_RND) * CELL_GRAN_RND; + + print_value(2, "[H PIXELS RND]", h_pixels_rnd); + + + /* 2.5th Calculation, aspect_ratio & v_sync_rnd + * + * [ASPECT_RATIO] = IF(H_PIXELS_RND = CELL_GRAN_RND*ROUND((V_LINES* + * 4.0/3.0)/CELL_GRAN_RND),"4:3") + * etc... + * [V_SYNC] = [value from table based on aspect ratio] + * [V_SYNC_RND] = ROUND(V_SYNC,0) // Not needed in principle + */ + + if (h_pixels_rnd == CELL_GRAN_RND * floor(((float)v_lines * 4.0 / 3.0) + / CELL_GRAN_RND)) { + aspect_ratio = "4:3"; + v_sync = 4; + } else if (h_pixels_rnd == CELL_GRAN_RND * floor(((float)v_lines * 16.0 + / 9.0) / CELL_GRAN_RND)) { + aspect_ratio = "16:9"; + v_sync = 5; + } else if (h_pixels_rnd == CELL_GRAN_RND * floor(((float)v_lines * 16.0 + / 10.0) / CELL_GRAN_RND)) { + aspect_ratio = "16:10"; + v_sync = 6; + } else if (h_pixels_rnd == CELL_GRAN_RND * floor(((float)v_lines * 5.0 + / 4.0) / CELL_GRAN_RND)) { + aspect_ratio = "5:4"; + v_sync = 7; + } else if (h_pixels_rnd == CELL_GRAN_RND * floor(((float)v_lines * 15.0 + / 9.0) / CELL_GRAN_RND)) { + aspect_ratio = "15:9"; + v_sync = 7; + } else { + /* Default case of unknown aspect ratio */ + aspect_ratio = "Custom"; + v_sync = 10; + } + v_sync_rnd = v_sync; + + /* + * 3. Determine Left & Right Borders + * + * Calculate the margins on the left and right side. + * + * [LEFT MARGIN (PIXELS)] = (IF( [MARGINS RQD?]="Y", + * (ROUNDDOWN( ([H PIXELS RND] * [MARGIN%] / 100 / + * [CELL GRAN RND]),0)) * [CELL GRAN RND], + * 0)) + * [RIGHT MARGIN (PIXELS)] = (IF( [MARGINS RQD?]="Y", + * (ROUNDDOWN( ([H PIXELS RND] * [MARGIN%] / 100 / + * [CELL GRAN RND]),0)) * [CELL GRAN RND], + * 0)) + */ + + left_margin = margins ? + floor(h_pixels_rnd * MARGIN_PERCENT / 100.0 / CELL_GRAN_RND) + * CELL_GRAN_RND : 0.0; + right_margin = left_margin; + + print_value(3, "[LEFT MARGIN (PIXELS)]", left_margin); + print_value(3, "[RIGHT MARGIN (PIXELS)]", right_margin); + + + /* 4. Find total active pixels. + * + * Find total number of active pixels in image and left and right + * margins. + * + * [TOTAL ACTIVE PIXELS] = [H PIXELS RND] + [LEFT MARGIN (PIXELS)] + + * [RIGHT MARGIN (PIXELS)] + */ + + total_active_pixels = h_pixels_rnd + left_margin + right_margin; + + print_value(4, "[TOTAL ACTIVE PIXELS]", total_active_pixels); + + + /* 5. Find number of lines per field. + * + * If interlace is requested, the number of vertical lines assumed + * by the calculation must be halved, as the computation calculates + * the number of vertical lines per field. In either case, the + * number of lines is rounded to the nearest integer. + * + * [V LINES RND] = IF([INT RQD?]="y", ROUNDDOWN([V LINES]/2,0), + * ROUNDDOWN([V LINES],0)) + */ + + v_lines_rnd = interlaced ? + floor((float) v_lines / 2.0) : + floor((float) v_lines); + + print_value(5, "[V LINES RND]", v_lines_rnd); + + + /* 6. Find Top and Bottom margins. + * + * [TOP MARGIN (LINES)] = IF([MARGINS RQD?]="Y", + * ROUNDDOWN(([MARGIN%]/100*[V LINES RND]),0), + * 0) + * [BOT MARGIN (LINES)] = IF([MARGINS RQD?]="Y", + * ROUNDDOWN(([MARGIN%]/100*[V LINES RND]),0), + * 0) + */ + + top_margin = margins ? floor(MARGIN_PERCENT / 100.0 * v_lines_rnd) : (0.0); + bot_margin = top_margin; + + print_value(6, "[TOP MARGIN (LINES)]", top_margin); + print_value(6, "[BOT MARGIN (LINES)]", bot_margin); + + + /* 7. If interlace is required, then set variable [INTERLACE]=0.5: + * + * [INTERLACE]=(IF([INT RQD?]="y",0.5,0)) + */ + + interlace = interlaced ? 0.5 : 0.0; + + print_value(7, "[INTERLACE]", interlace); + + + /* + * Here it diverges for "reduced blanking" or normal blanking modes. + */ + + if (reduced_blank) { + h_blank = RB_H_BLANK; + + + /* 8. Estimate Horiz. Period (us). + * + * [H PERIOD EST] = ((1000000/V_FIELD_RATE_RQD)-RB_MIN_V_BLANK)/(V_LINES_RND+TOP_MARGIN+BOT_MARGIN) + */ + + h_period_est = (1000000.0/v_field_rate_rqd - RB_MIN_V_BLANK) + / (v_lines_rnd + top_margin + bot_margin); + + print_value(8, "[H PERIOD EST]", h_period_est); + + + /* 9. Find number of lines in vertical blanking. + * + * [Actual VBI_LINES] = RB_MIN_V_BLANK/H_PERIOD_EST + * [VBI_LINES] = ROUNDDOWN(RB_MIN_V_BLANK/H_PERIOD_EST,0) + 1 + */ + + vbi_lines = RB_MIN_V_BLANK/h_period_est; + print_value(9, "[Actual VBI LINES]", vbi_lines); + + vbi_lines = floor(vbi_lines) + 1.0; + print_value(9, "[VBI LINES]", vbi_lines); + + + /* 10. Check Vertical Blanking is sufficient. + * + * [RB MIN VBI] = RB_V_FPORCH+V_SYNC_RND+RB_MIN_V_BPORCH + * [ACT VBI LINES] = IF(VBI_LINES<RB_MIN_VBI,RB_MIN_VBI,VBI_LINES) + */ + + rb_min_vbi = RB_V_FPORCH + v_sync_rnd + RB_MIN_V_BPORCH; + act_vbi_lines = (vbi_lines < rb_min_vbi) ? rb_min_vbi : vbi_lines; + + print_value(10, "[Minimum VBI Lines]", rb_min_vbi); + print_value(10, "[ACT VBI LINES]", act_vbi_lines); + + + /* 11. Find total number of lines in vertical field. + * + * [TOTAL V LINES] = ACT_VBI_LINES+V_LINES_RND+TOP_MARGIN+BOT_MARGIN+INTERLACE + */ + + total_v_lines = act_vbi_lines + v_lines_rnd + top_margin + + bot_margin + interlace; + + print_value(11, "[TOTAL V LINES]", total_v_lines); + + + /* 12. Find total number of pixels in a line (pixels). + * + * [TOTAL PIXELS] = RB_H_BLANK+TOTAL_ACTIVE_PIXELS + */ + + total_pixels = total_active_pixels + RB_H_BLANK; + + print_value(12, "[TOTAL PIXELS]", total_pixels); + + + /* 13. Find Pixel Clock Frequency (MHz). + * + * [Non-rounded PIXEL_FREQ] = V_FIELD_RATE_RQD*TOTAL_V_LINES*TOTAL_PIXELS/1000000 + * [ACT PIXEL FREQ] = CLOCK_STEP * ROUND((V_FIELD_RATE_RQD*TOTAL_V_LINES*TOTAL_PIXELS/1000000)/CLOCK_STEP,0) + */ + + act_pixel_freq = v_field_rate_rqd * total_v_lines + * total_pixels / 1000000.0; + print_value(13, "[Non-rounded PIXEL FREQ]", act_pixel_freq); + + act_pixel_freq = CLOCK_STEP * floor(act_pixel_freq / CLOCK_STEP); + print_value(13, "[ACT PIXEL FREQ]", act_pixel_freq); + + + stage = 14; + + } else { /* Normal Blanking */ + + /* 8. Estimate Horiz. Period (us). + * + * [H PERIOD EST] = ((1/V_FIELD_RATE_RQD)-MIN_VSYNC_BP/1000000)/(V_LINES_RND+(2*TOP_MARGIN)+MIN_V_PORCH_RND+INTERLACE)*1000000 + */ + + h_period_est = ((1/v_field_rate_rqd) - MIN_VSYNC_BP/1000000.0) + / (v_lines_rnd + (2*top_margin) + MIN_V_PORCH_RND + interlace) + * 1000000.0; + + print_value(8, "[H PERIOD EST]", h_period_est); + + + /* 9. Find number of lines in (SYNC + BACK PORCH). + * + * [Estimated V_SYNC_BP] = ROUNDDOWN((MIN_VSYNC_BP/H_PERIOD_EST),0)+1 + * [Actual V_SYNC_BP] = MIN_VSYNC_BP/H_PERIOD_EST + * [V_SYNC_BP] = IF(Estimated V_SYNC_BP<(V_SYNC+MIN_V_BPORCH), + * V_SYNC+MIN_V_BPORCH,Estimated V_SYNC_BP) + */ + + v_sync_bp = MIN_VSYNC_BP/h_period_est; + print_value(9, "[Actual V_SYNC_BP]", v_sync_bp); + + v_sync_bp = floor(v_sync_bp) + 1; + print_value(9, "[Estimated V_SYNC_BP]", v_sync_bp); + + v_sync_bp = (v_sync_bp < v_sync + MIN_V_BPORCH) ? + v_sync + MIN_V_BPORCH : v_sync_bp; + print_value(9, "[V_SYNC_BP]", v_sync_bp); + + + /* 10. Find number of lines in back porch (Lines). + * + * [Back porch] = V_SYNC_BP - V_SYNC_RND; + */ + + print_value(10, "[Back porch]", v_sync_bp - v_sync_rnd); + + + /* 11. Find total number of lines in vertical field. + * + * [TOTAL V LINES] = V_LINES_RND+TOP_MARGIN+BOT_MARGIN + * +V_SYNC_BP+INTERLACE+MIN_V_PORCH_RND + */ + + total_v_lines = v_lines_rnd + top_margin + bot_margin + + v_sync_bp + interlace + MIN_V_PORCH_RND; + + print_value(11, "[TOTAL V LINES]", total_v_lines); + + + /* 12. Find ideal blanking duty cycle from formula (%): + * + * [IDEAL DUTY CYCLE] = C_PRIME-(M_PRIME*H_PERIOD_EST/1000) + */ + + ideal_duty_cycle = C_PRIME - (M_PRIME * h_period_est / 1000.0); + + print_value(12, "[IDEAL DUTY CYCLE]", ideal_duty_cycle); + + + /* 13. Find blanking time to nearest cell (Pixels). + * + * [H BLANK] = IF(IDEAL_DUTY_CYCLE<20,(ROUNDDOWN((TOTAL_ACTIVE_PIXELS*20/(100-20)/(2*CELL_GRAN_RND)),0))*(2*CELL_GRAN_RND),(ROUNDDOWN((TOTAL_ACTIVE_PIXELS*IDEAL_DUTY_CYCLE/(100-IDEAL_DUTY_CYCLE)/(2*CELL_GRAN_RND)),0))*(2*CELL_GRAN_RND)) + */ + + cur_duty_cycle = (ideal_duty_cycle < 20.0) ? 20.0 : ideal_duty_cycle; + h_blank = floor((total_active_pixels * cur_duty_cycle/(100.0 - cur_duty_cycle)/(2.0*CELL_GRAN_RND))) * (2.0*CELL_GRAN_RND); + + print_value(13, "[H BLANK]", h_blank); + + + /* 14. Find total number of pixels in a line (Pixels). + * + * [TOTAL PIXELS] = TOTAL_ACTIVE_PIXELS + H_BLANK + */ + + total_pixels = total_active_pixels + h_blank; + + print_value(14, "[TOTAL PIXELS]", total_pixels); + + + /* 15. Find pixel clock frequency (MHz). + * + * [Non-rounded PIXEL FREQ] = TOTAL_PIXELS / H_PERIOD_EST + * [ACT PIXEL FREQ] = CLOCK_STEP * ROUNDDOWN( + */ + + act_pixel_freq = total_pixels / h_period_est; + print_value(15, "[Non-rounded PIXEL FREQ]", act_pixel_freq); + + act_pixel_freq = CLOCK_STEP * floor(act_pixel_freq / CLOCK_STEP); + print_value(15, "[ACT PIXEL FREQ]", act_pixel_freq); + + + stage = 16; + } + + + /* 14/16. Find actual horizontal frequency (kHz) + * + * [ACT H FREQ] = 1000*ACT_PIXEL_FREQ/TOTAL_PIXELS + */ + + act_h_freq = 1000 * act_pixel_freq / total_pixels; + + print_value(stage, "[ACT H FREQ]", act_h_freq); + stage += 1; + + + /* 15/17. Find actual field rate (Hz) + * + * [ACT FIELD RATE] = 1000*ACT_H_FREQ/TOTAL_V_LINES + */ + + act_field_rate = 1000 * act_h_freq / total_v_lines; + + print_value(stage, "[ACT FIELD RATE]", act_field_rate); + stage += 1; + + + /* 16/18. Find actual vertical frame frequency (Hz) + * + * [ACT FRAME RATE] = IF(INT_RQD?=Y,ACT_FIELD_RATE/2,ACT_FIELD_RATE) + */ + + act_frame_rate = interlace ? + (act_field_rate / 2) : act_field_rate; + + print_value(stage, "[ACT FRAME RATE]", act_frame_rate); + + + + /* + * Extra computations not numbered in the CVT spreadsheet. + */ + + + /* 20. Find Horizontal Back Porch. + * + * [H BACK PORCH] = H_BLANK/2 + */ + + h_back_porch = h_blank/2; + + print_value(20, "[H BACK PORCH]", h_back_porch); + + + /* 21. Find Horizontal Front Porch. + * + * [H SYNC RND] = IF(RED_BLANK_RQD?="Y",RB_H_SYNC,(ROUNDDOWN((H_SYNC_PER/100*TOTAL_PIXELS/CELL_GRAN_RND),0))*CELL_GRAN_RND) + */ + + if (reduced_blank) { + h_sync_rnd = RB_H_SYNC; + } else { + h_sync_rnd = floor(H_SYNC_PER/100.0*total_pixels/CELL_GRAN_RND) + * CELL_GRAN_RND; + } + + print_value(21, "[H SYNC RND]", h_sync_rnd); + + + /* 22. Find Horizontal Front Porch. + * + * [H FRONT PORCH] = H_BLANK - H_BACK_PORCH - H_SYNC_RND + */ + + h_front_porch = h_blank - h_back_porch - h_sync_rnd; + + print_value(22, "[H FRONT PORCH]", h_front_porch); + + + /* 23. Find Vertical Front Porch. + * + * [V FRONT PORCH] = IF(RED_BLANK_RQD?="y",RB_V_FPORCH,MIN_V_PORCH_RND) + */ + + v_front_porch = reduced_blank ? RB_V_FPORCH : MIN_V_PORCH_RND; + + print_value(23, "[V FRONT PORCH]", v_front_porch); + + + + /* finally, pack the results in the mode struct */ + + m->hr = (int) (h_pixels_rnd); + m->hss = (int) (h_pixels_rnd + h_front_porch); + m->hse = (int) (h_pixels_rnd + h_front_porch + h_sync_rnd); + m->hfl = (int) (total_pixels); + +#if 0 + m->vr = (int) (v_lines_rnd); + m->vss = (int) (v_lines_rnd + v_front_porch); + m->vse = (int) (v_lines_rnd + v_front_porch + v_sync_rnd); + m->vfl = (int) (total_v_lines); +#else + { + int real_v_lines = v_lines; + m->vr = (int) (real_v_lines); + m->vss = (int) (real_v_lines + v_front_porch); + m->vse = (int) (real_v_lines + v_front_porch + v_sync_rnd); + m->vfl = (int) (total_v_lines - v_lines_rnd + real_v_lines); + } +#endif + + m->pclk = act_pixel_freq; + m->h_freq = act_h_freq; + m->v_freq = freq; + m->real_v_rate = act_field_rate; + + m->in = interlaced; + m->rb = reduced_blank; + + return (m); + +} // vert_refresh() + +#ifdef __cplusplus +} +#endif diff --git a/src/cvt.h b/src/cvt.h new file mode 100644 index 0000000..7f2ce74 --- /dev/null +++ b/src/cvt.h @@ -0,0 +1,26 @@ +#ifndef __CVT_H_ +#define __CVT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* struct definitions */ + +typedef struct __mode +{ + int hr, hss, hse, hfl; + int vr, vss, vse, vfl; + float pclk, h_freq, v_freq; + float real_v_rate; + int rb, in; +} mode; + +mode *vert_refresh (int h_pixels, int v_lines, float freq, + int interlaced, int reduced_blank, int margins); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/i18n/de.ts b/src/i18n/de.ts index 6e4a48a..ef4f6fc 100644 --- a/src/i18n/de.ts +++ b/src/i18n/de.ts @@ -1,30 +1,86 @@ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> -<TS version="2.1" language="de_DE"> +<TS version="2.1"> <context> <name>TimeOutDialog</name> <message> <location filename="../timeoutdialog.cpp" line="16"/> <source>%v seconds</source> - <translation>Reset in %v Sekunden</translation> + <translation type="unfinished"></translation> </message> </context> <context> <name>Widget</name> <message> - <location filename="../widget.ui" line="28"/> + <location filename="../widget.ui" line="29"/> + <source>Clone</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../widget.ui" line="35"/> + <source>In this mode, all connected outputs will display the same image. This is most useful if there is one monitor and one projector currently connected to the computer.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../widget.ui" line="58"/> + <source>Please select the mode you want to apply. The mode that has been detected as the potentially best mode is highlighted.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../widget.ui" line="86"/> + <location filename="../widget.ui" line="171"/> + <location filename="../widget.ui" line="208"/> <source>Apply</source> - <translation>Anwenden</translation> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../widget.ui" line="102"/> + <location filename="../widget.cpp" line="40"/> + <source>Dual Screen</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../widget.ui" line="127"/> + <source><- Swap -></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../widget.ui" line="181"/> + <source>Advanced Setup</source> + <translation type="unfinished"></translation> </message> <message> - <location filename="../widget.cpp" line="394"/> + <location filename="../widget.cpp" line="145"/> <source>Do you want to keep this resolution?</source> - <translation>Möchten Sie diese Auflösung beibehalten?</translation> + <translation type="unfinished"></translation> </message> <message> - <location filename="../widget.cpp" line="395"/> + <location filename="../widget.cpp" line="146"/> <source>Keep</source> - <translation>Beibehalten</translation> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>main</name> + <message> + <location filename="../main.cpp" line="70"/> + <source>Automatically configure modes and set up screens.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../main.cpp" line="74"/> + <source>Show config GUI if more than one screen is connected.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../main.cpp" line="78"/> + <source>Keep running in background and show GUI again when number of screens changes.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../main.cpp" line="82"/> + <source>Test mode, don't actually apply any changes.</source> + <translation type="unfinished"></translation> </message> </context> </TS> diff --git a/src/main.cpp b/src/main.cpp index f0c2dba..1493bcd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,23 +1,90 @@ -// Copyright 2013, University of Freiburg, -// Author: Manuel Schneider <ms1144> - -#include <QtWidgets/QApplication> // for Qt5 #include "widget.h" +#include "main.h" +#include "x.h" + +#include <QApplication> +#include <QLibraryInfo> +#include <QTranslator> +#include <QCommandLineParser> + +namespace { +bool _testMode, _autoSetup, _showGui, _backgroundMode; +} + +namespace CommandLine +{ +bool testMode() { return _testMode; } +bool autoSetup() { return _autoSetup; } +bool showGui() { return _showGui; } +bool backgroundMode() { return _backgroundMode; } +} + +static void parseCommandLine(const QApplication &a); int main(int argc, char *argv[]) { - QApplication a(argc, argv); - // System strings - QTranslator *qtTranslator = new QTranslator(&a); - qtTranslator->load(QLocale::system(), "qt", "_", QLibraryInfo::location(QLibraryInfo::TranslationsPath)); - a.installTranslator(qtTranslator); - // App specific - QTranslator *translator = new QTranslator(&a); - translator->load(QLocale::system(), ":"); - a.installTranslator(translator); - Widget w(argc == 2 && QString(argv[1]) == QString("--test")); - w.show(); - return a.exec(); + QApplication a(argc, argv); + QCoreApplication::setApplicationName("BeamerGUI XP - Home Edition"); + QCoreApplication::setApplicationVersion("2.0"); + // System strings + QTranslator *qtTranslator = new QTranslator(&a); + qtTranslator->load(QLocale::system(), "qt", "_", QLibraryInfo::location(QLibraryInfo::TranslationsPath)); + a.installTranslator(qtTranslator); + // App specific + QTranslator *translator = new QTranslator(&a); + translator->load(QLocale::system(), ":"); + a.installTranslator(translator); + + parseCommandLine(a); + + ScreenMode currentMode; + if (CommandLine::autoSetup()) { + currentMode = ScreenSetup::inst()->setDefaultMode(CommandLine::testMode()); + } else { + currentMode = ScreenSetup::inst()->getCurrentMode(); + } + + bool showNow = (CommandLine::showGui() && currentMode != ScreenMode::Single); + + Widget *w = nullptr; + if (CommandLine::backgroundMode() || showNow) { + w = new Widget(); + if (showNow) { + w->show(); + } + } + if (w == nullptr) + return 0; + return a.exec(); } -//////////////////////////////////////////////////////////////////////////////// +static void parseCommandLine(const QApplication &a) +{ + // Command line + QCommandLineParser parser; + parser.setApplicationDescription("Utility for detecting and configuring screen setup"); + parser.addHelpOption(); + parser.addVersionOption(); + // Option for adding modes and trying to auto-setup + QCommandLineOption oAutoSetup(QStringList() << "a" << "auto", + QCoreApplication::translate("main", "Automatically configure modes and set up screens.")); + parser.addOption(oAutoSetup); + // Show config GUI if more than one screen + QCommandLineOption oShowGui(QStringList() << "g" << "gui", + QCoreApplication::translate("main", "Show config GUI if more than one screen is connected.")); + parser.addOption(oShowGui); + // Keep running and detect screen setup changes + QCommandLineOption oBackground(QStringList() << "b" << "background", + QCoreApplication::translate("main", "Keep running in background and show GUI again when number of screens changes.")); + parser.addOption(oBackground); + // Test mode -- pretend to do setup + QCommandLineOption oTest(QStringList() << "t" << "test", + QCoreApplication::translate("main", "Test mode, don't actually apply any changes.")); + parser.addOption(oTest); + // PARSE + parser.process(a); + _testMode = parser.isSet(oTest); + _autoSetup = parser.isSet(oAutoSetup); + _showGui = parser.isSet(oShowGui); + _backgroundMode = parser.isSet(oBackground); +} diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..9aa2d8d --- /dev/null +++ b/src/main.h @@ -0,0 +1,12 @@ +#ifndef MAIN_H +#define MAIN_H + +namespace CommandLine +{ +bool testMode(); +bool autoSetup(); +bool showGui(); +bool backgroundMode(); +} + +#endif // MAIN_H diff --git a/src/widget.cpp b/src/widget.cpp index bef88db..751c69d 100644 --- a/src/widget.cpp +++ b/src/widget.cpp @@ -1,383 +1,126 @@ -// Copyright 2013, University of Freiburg, -// Author: Manuel Schneider <ms1144> - -#include <iostream> -#include <algorithm> - -#include <QDebug> -#include <QtWidgets/QAction> - #include "widget.h" #include "ui_widget.h" #include "timeoutdialog.h" -#include "math.h" +#include "x.h" +#include "main.h" + +#include <QDebug> +#include <QtWidgets/QAction> //______________________________________________________________________________ -Widget::Widget(bool testMode, QWidget *parent) : - QWidget(parent), - _ui(new Ui::Widget) +Widget::Widget(QWidget *parent) : + QWidget(parent), + _ui(new Ui::Widget) { - _ui->setupUi(this); - - // Get initial data (to be freed) - _display = XOpenDisplay(NULL); - _screenResources = XRRGetScreenResourcesCurrent( - _display, - DefaultRootWindow(_display)); - - // Get the information about the X elements - updateScreenResources(); - - if (testMode) { - while (_connectedOutputList.size() > 2) { - _connectedOutputList.pop_front(); - } - } - - switch ( _connectedOutputList.size() ){ - /*************************************************************************/ - case 1:// In case of one connected output - xrandr --auto - qDebug() << "One connected output"; - exit(0); - break; - case 2: // In case of two connected outputs - - qDebug() << "Two connected outputs"; - - // Check if we are in clone mode - if (testMode || cloneMode()) { - - qDebug() << "Dual output with cloned screen!"; - - // TODO make that mess nice and clean - double_t w0 = _outputMap[_connectedOutputList[0]]->mm_width; - double_t h0 = _outputMap[_connectedOutputList[0]]->mm_height; - - double_t w1 = _outputMap[_connectedOutputList[1]]->mm_width; - double_t h1 = _outputMap[_connectedOutputList[1]]->mm_height; - - // Get a human readable reference - if (w0 == 0 && h0 == 0) { - _beamer = _connectedOutputList[0]; - _monitor = _connectedOutputList[1]; - } else if (w1 == 0 && h1 == 0) { - _beamer = _connectedOutputList[1]; - _monitor = _connectedOutputList[0]; - } else { - double_t d0 = sqrt((pow(w0, 2) + pow(h0, 2))); - double_t d1 = sqrt((pow(w1, 2) + pow(h1, 2))); - - if (d0 > d1) { - _beamer = _connectedOutputList[0]; - _monitor = _connectedOutputList[1]; - } else { - _beamer = _connectedOutputList[1]; - _monitor = _connectedOutputList[0]; - } - } - - // Intersect them by the resolution sorted, dont care about O(n³) - QList<QPair<XRRModeInfo*, XRRModeInfo*> > commonModes; - - // Iterate over the modes the monitor supports - for (int i = 0; i < _outputMap[_monitor]->nmode; ++i) { - - XRRModeInfo* monitorMode = _modeMap[_outputMap[_monitor]->modes[i]]; - - // Skip interlace modes - if ( monitorMode->modeFlags & RR_Interlace ) - continue; - - // Iterate over the modes the beamer supports - for (int j = 0; j < _outputMap[_beamer]->nmode; ++j) { - - XRRModeInfo* beamerMode = _modeMap[_outputMap[_beamer]->modes[j]]; - - // Skip interlace modes - if ( beamerMode->modeFlags & RR_Interlace ) - continue; - - // Only if the modes have the same size, list them in common modes - if ( monitorMode->height == beamerMode->height - && monitorMode->width == beamerMode->width ) { - - // Build a sorted list of common modes in descending order - QList<QPair<XRRModeInfo*, XRRModeInfo*> >::iterator k - = commonModes.begin(); - for (;;++k) { - - // If at the end, the mode to insert is the smallest, insert. - // This has to be first to avoid segfaults - if (k == commonModes.end()) { - - commonModes.insert(k, qMakePair (monitorMode, beamerMode)); - break; - } - - // If the mode to insert is larger than k, insert. - if ( monitorMode->width > k->first->width) { - commonModes.insert(k, qMakePair (monitorMode, beamerMode)); - break; - } - - // If the width is the same ... - if ( monitorMode->width == k->first->width ) { - - // ... and the height is the same, the mode already exists - if ( monitorMode->height == k->first->height ) { - break; - } - - // ... and the height is larger, insert. - if ( monitorMode->height > k->first->height ) { - commonModes.insert(k, qMakePair (monitorMode, beamerMode)); - break; - } - } - } - } - } - } - - - // Check if the beamer transmitted reliable data. - bool gotEDID = false; - int nprop; - Atom *props = XRRListOutputProperties(_display, _beamer, &nprop); - for (int i = 0; i < nprop; ++i) { - char *atom_name = XGetAtomName (_display, props[i]); - if ( strcmp (atom_name, "EDID") == 0) { - gotEDID = true; - XFree (atom_name); - break; - } - XFree (atom_name); - } - free(props); - - // If the beamer transmits no reliable EDID data add modes -#ifdef QT_DEBUG - gotEDID = false; -#endif - if (gotEDID) { - - qDebug() << "GOT EDID!"; - - // Extract the preferred mode of the beamer - RRMode preferredBeamerModeId = 0; - for (int i = 0; i < _outputMap[_beamer]->nmode; ++i) { - if (i < _outputMap[_beamer]->npreferred) { - preferredBeamerModeId = _outputMap[_beamer]->modes[i]; - break; - } - } - - // Compute the aspect ratio of the beamer - float aspectRatio = (float)_modeMap[preferredBeamerModeId]->width - / _modeMap[preferredBeamerModeId]->height; - - // Fill widget with data - for (QList<QPair<XRRModeInfo*, XRRModeInfo*> >::iterator i - = commonModes.begin(); i != commonModes.end(); ++i ) { - float modeAspectRatio = ((float)i->first->width / i->first->height); - if ( abs(modeAspectRatio - aspectRatio) < 0.05 ) // APPROX - _ui->comboBox->addItem(i->first->name, - QList<QVariant>() - << QVariant((qulonglong)i->first->id) - << QVariant((qulonglong)i->second->id)); - } - } else { - - qDebug() << "NO EDID!"; - - // Fill widget with data without AR match - // Fill widget with data - for ( QList<QPair<XRRModeInfo*, XRRModeInfo*> >::iterator i - = commonModes.begin(); i != commonModes.end(); ++i ) { - qDebug() << "Insert into QComboBox" - << i->first->width << "x" << i->first->height - << "(" << i->first->id << ")"; - _ui->comboBox->addItem(i->first->name, - QList<QVariant>() - << QVariant((qulonglong)i->first->id) - << QVariant((qulonglong)i->second->id)); - } - } - - // Set the current resolution highlighted - QString n = _modeMap[_crtcMap[_outputMap[_monitor]->crtc]->mode]->name; - int index = _ui->comboBox->findText(n); - _ui->comboBox->setCurrentIndex(index); - - // Remove borders and stuff - setWindowFlags(Qt::Widget | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); - //setStyleSheet("background:transparent;"); - //setAttribute(Qt::WA_TranslucentBackground); - - // Resize widget to its content - resize(sizeHint()); - show(); - QTimer *timer = new QTimer(this); - connect(timer, SIGNAL(timeout()), this, SLOT(bringToTopTimer())); - timer->start(1000); - - // Center dialog on screenbottom - const QRect desktopRect = QApplication::desktop()->screenGeometry(); - this->move( desktopRect.width()/2-this->width()/2, - desktopRect.height()-this->height()); - - // Connect button signal to appropriate slot - connect(_ui->pushButton, SIGNAL(clicked()), this, SLOT(handleButton())); - } - /*********************************************************************/ - // If NEITHER of the outputs is a beamer (likely dualscreen setup) - else { - // TODO(manuel): Future feature. Setup dualscreen - qDebug() << "Dual output with extended screen!"; - exit(0); - } - break; - /*************************************************************************/ - default: - // If there are more than 3 outputs - // its up to the user. Quit. - qDebug() << "More than two outputs. Quit."; - exit(0); - break; - } - /*************************************************************************/ + _ui->setupUi(this); + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); } void Widget::bringToTopTimer() { - if (_ui->comboBox->view()->isVisible()) return; - raise(); + auto combos = this->findChildren<QComboBox*>(); + for (auto combo : combos) { + if (combo->view()->isVisible()) + return; + } + raise(); } //______________________________________________________________________________ -void Widget::updateScreenResources() -{ - // Clear the modemap (nothing to be freed, stored in screenResources) - _modeMap.clear(); - - // Create the modemap - qDebug() << "_MODES_"; - for (int i = 0; i < _screenResources->nmode; ++i) { - _modeMap.insert( - _screenResources->modes[i].id, - &_screenResources->modes[i]); - qDebug() << _screenResources->modes[i].id << "\t" - << _screenResources->modes[i].width << "x" - << _screenResources->modes[i].height; - } - - // Clear the crtmap - for (CrtcMap::iterator it = _crtcMap.begin(); - it != _crtcMap.end();) { - XRRFreeCrtcInfo(*it); - it = _crtcMap.erase(it); - } - - // Create crtcMap - for (int i = 0; i < _screenResources->ncrtc; ++i) { - XRRCrtcInfo * info = XRRGetCrtcInfo( - _display, - _screenResources, - _screenResources->crtcs[i]); - _crtcMap.insert( - _screenResources->crtcs[i], - info); - } - - // Clear the outputmap - for (OutputMap::iterator it = _outputMap.begin(); - it != _outputMap.end();) { - XRRFreeOutputInfo(*it); - it = _outputMap.erase(it); - } - - // Create outputmap and connectedOutputMap - for (int i = 0; i < _screenResources->noutput; ++i) { - XRROutputInfo* info = XRRGetOutputInfo( - _display, - _screenResources, - _screenResources->outputs[i]); - _outputMap.insert( - _screenResources->outputs[i], - info); +Widget::~Widget() { - // Store the connected ones separate - if ( info->connection == RR_Connected ) - _connectedOutputList.push_back(_screenResources->outputs[i]); - } } - -//______________________________________________________________________________ -Widget::~Widget() { - delete _ui; - XCloseDisplay(_display); - XRRFreeScreenResources(_screenResources); +void Widget::showEvent(QShowEvent *event) +{ + QWidget::showEvent(event); + ScreenMode mode = ScreenSetup::inst()->getCurrentMode(); + if (ScreenSetup::inst()->getOutputCount() == 2 || mode == ScreenMode::Dual) { + if (_ui->tabWidget->widget(1) != _ui->tabDual) { + _ui->tabWidget->insertTab(1, _ui->tabDual, tr("Dual Screen")); + } + } else { + if (_ui->tabWidget->widget(1) == _ui->tabDual) { + _ui->tabWidget->removeTab(1); + } + } + switch (mode) { + case ScreenMode::Single: + case ScreenMode::Clone: + _ui->tabWidget->setCurrentWidget(_ui->tabClone); + break; + case ScreenMode::Dual: + _ui->tabWidget->setCurrentWidget(_ui->tabDual); + break; + case ScreenMode::Advanced: + _ui->tabWidget->setCurrentWidget(_ui->tabAdvanced); + break; + } + initControls(); } +void Widget::initControls() +{ + auto modes = ScreenSetup::inst()->getCommonModes(); + for (auto mode : modes) { + _ui->cboCloneResolution->addItem(QString::asprintf("%ux%u", mode.first, mode.second)); + } + // Clear advanced controls + auto widgets = _ui->advancedCombos->findChildren<QWidget*>(); + for (auto w : widgets) { + _ui->advancedCombos->removeWidget(w); + w->deleteLater(); + } + // Create new + QHash<QString, int> positions = ScreenSetup::inst()->getScreenPositions(); + ScreenSetup::ResolutionVector resolutions = ScreenSetup::inst()->getVirtualResolutions(); + int row = 0; + for (auto it = positions.begin(); it != positions.end(); ++it) { + _ui->advancedCombos->addWidget(new QLabel(it.key()), row, 0, 1, -1); + _ui->advancedCombos->addWidget(new QComboBox(), row, 2); + row++; + } +} //______________________________________________________________________________ -void Widget::handleButton(){ +void Widget::handleButton() { + // Apply - /*************************** Backup the crtcinfos ***************************/ - - qDebug() << "Backup the crtc infos"; - - CrtcMap backup; - for ( CrtcMap::iterator it = _crtcMap.begin(); - it != _crtcMap.end(); ++it ) { - backup[it.key()] = new XRRCrtcInfo; - backup[it.key()]->x = it.value()->x; - backup[it.key()]->y = it.value()->y; - backup[it.key()]->mode = it.value()->mode; - backup[it.key()]->rotation = it.value()->rotation; - backup[it.key()]->noutput = it.value()->noutput; - backup[it.key()]->outputs = new RROutput[it.value()->noutput]; - for (int i = 0; i < it.value()->noutput; ++i) { - backup[it.key()]->outputs[i] = it.value()->outputs[i]; - } - } - - /**************************** Apply the resolution **************************/ + /* // Get the modes which has to be applied from QComboBox QList<QVariant> modes = - _ui->comboBox->itemData( _ui->comboBox->currentIndex()).toList(); + _ui->comboBox->itemData( _ui->comboBox->currentIndex()).toList(); XRRModeInfo* monitorMode = _modeMap[modes[0].toULongLong()]; XRRModeInfo* beamerMode = _modeMap[modes[1].toULongLong()]; // First disconnect all crts to avoid conflicts for(CrtcMap::iterator it = _crtcMap.begin(); it != _crtcMap.end(); ++it) { Status st = XRRSetCrtcConfig(_display, _screenResources, it.key(), CurrentTime, - 0, 0, None, RR_Rotate_0, NULL, 0); + 0, 0, None, RR_Rotate_0, nullptr, 0); qDebug() << "Disconnecting" << it.key() << ":" << st; } // Set screensize XRRSetScreenSize(_display, DefaultRootWindow(_display), - monitorMode->width, monitorMode->height, - 25.4 * monitorMode->width / 96, // standard dpi that X uses - 25.4 * monitorMode->height / 96); // standard dpi that X uses + monitorMode->width, monitorMode->height, + 25.4 * monitorMode->width / 96, // standard dpi that X uses + 25.4 * monitorMode->height / 96); // standard dpi that X uses // Apply the modes Status stMon = XRRSetCrtcConfig(_display, _screenResources, - _outputMap[_monitor]->crtc, - CurrentTime, - 0, 0, monitorMode->id, - RR_Rotate_0, - &_monitor, 1); + _outputMap[_monitor]->crtc, + CurrentTime, + 0, 0, monitorMode->id, + RR_Rotate_0, + &_monitor, 1); Status stBem = XRRSetCrtcConfig(_display, - _screenResources, - _outputMap[_beamer]->crtc, - CurrentTime, - 0, 0, beamerMode->id, - RR_Rotate_0, + _screenResources, + _outputMap[_beamer]->crtc, + CurrentTime, + 0, 0, beamerMode->id, + RR_Rotate_0, &_beamer, 1); // Sync... whatever... @@ -391,112 +134,93 @@ void Widget::handleButton(){ // Set the mouse pointer in the middle of the screen QCursor::setPos(monitorMode->width/2, monitorMode->height/2); - /*************************** ASK for confirmtion ****************************/ + */ + + /*************************** ASK for confirmtion ****************************/ - // Show a dialog asking if the res should be kept - TimeOutDialog keepDialog(15, this); - keepDialog.setWindowModality(Qt::ApplicationModal); - keepDialog.setWindowTitle(" "); - keepDialog.setLabelText(trUtf8("Do you want to keep this resolution?")); - keepDialog.setCancelButtonText(trUtf8("Keep")); + // Show a dialog asking if the res should be kept + TimeOutDialog keepDialog(15, this); + keepDialog.setWindowModality(Qt::ApplicationModal); + keepDialog.setWindowTitle(" "); + keepDialog.setLabelText(trUtf8("Do you want to keep this resolution?")); + keepDialog.setCancelButtonText(trUtf8("Keep")); + /* keepDialog.move(monitorMode->width/2 - this->width()/2, - monitorMode->height/2 - this->height()); - keepDialog.show(); + monitorMode->height/2 - this->height()); + */ + keepDialog.show(); - while (keepDialog.isActive()) { - QCoreApplication::processEvents(); - } + while (keepDialog.isActive()) { + QCoreApplication::processEvents(); + } + /* // If the dialog was not canceled revert the resolution if ( !keepDialog.wasCanceled()) { qDebug() << "User did not cancel timeout, reverting!"; - /**************************** Apply the backup ****************************/ - - // First disconnect all crts to avoid conflicts - for(CrtcMap::iterator it = _crtcMap.begin(); it != _crtcMap.end(); ++it) { - XRRSetCrtcConfig(_display, _screenResources, it.key(), CurrentTime, - 0, 0, None, RR_Rotate_0, NULL, 0); - } - - // Then calc backed up screensize - QSize ScreenSize(0,0); - for ( CrtcMap::iterator it = backup.begin(); - it != backup.end(); ++it ) { - // Dangerzone. Only access existing modes! - if ( it.value()->mode != None ) { - ScreenSize.setWidth( - std::max((uint)ScreenSize.width(), - it.value()->x+_modeMap[it.value()->mode]->width)); - ScreenSize.setHeight( - std::max((uint)ScreenSize.height(), - it.value()->y+_modeMap[it.value()->mode]->height)); - } - } - - // Set screensize - XRRSetScreenSize(_display, DefaultRootWindow(_display), - ScreenSize.width(), ScreenSize.height(), - 25.4 * ScreenSize.width() / 96, // dpi used by X - 25.4 * ScreenSize.height() / 96); // dpi used by X - - // Apply the backup - for ( CrtcMap::iterator it = backup.begin(); - it != backup.end(); ++it ) { - XRRSetCrtcConfig(_display, - _screenResources, - it.key(), - CurrentTime, - it.value()->x, - it.value()->y, - it.value()->mode, - it.value()->rotation, - it.value()->outputs, - it.value()->noutput); - } - - // Sync... whatever... - XSync (_display, False); - - // center dialog on screenbottom - qDebug() << "Again center dialog on screenbottom"; - this->move( ScreenSize.width()/2 - this->width()/2, - ScreenSize.height() - this->height()); - - // Set the mouse pointer in the middle of the screen - QCursor::setPos(monitorMode->width/2, monitorMode->height/2); + // First disconnect all crts to avoid conflicts + for(CrtcMap::iterator it = _crtcMap.begin(); it != _crtcMap.end(); ++it) { + XRRSetCrtcConfig(_display, _screenResources, it.key(), CurrentTime, + 0, 0, None, RR_Rotate_0, nullptr, 0); + } + + // Then calc backed up screensize + QSize ScreenSize(0,0); + for ( CrtcMap::iterator it = backup.begin(); + it != backup.end(); ++it ) { + // Dangerzone. Only access existing modes! + if ( it.value()->mode != None ) { + ScreenSize.setWidth( + std::max((uint)ScreenSize.width(), + it.value()->x+_modeMap[it.value()->mode]->width)); + ScreenSize.setHeight( + std::max((uint)ScreenSize.height(), + it.value()->y+_modeMap[it.value()->mode]->height)); + } + } + + // Set screensize + XRRSetScreenSize(_display, DefaultRootWindow(_display), + ScreenSize.width(), ScreenSize.height(), + 25.4 * ScreenSize.width() / 96, // dpi used by X + 25.4 * ScreenSize.height() / 96); // dpi used by X + + // Apply the backup + for ( CrtcMap::iterator it = backup.begin(); + it != backup.end(); ++it ) { + XRRSetCrtcConfig(_display, + _screenResources, + it.key(), + CurrentTime, + it.value()->x, + it.value()->y, + it.value()->mode, + it.value()->rotation, + it.value()->outputs, + it.value()->noutput); + } + + // Sync... whatever... + XSync (_display, False); + + // center dialog on screenbottom + qDebug() << "Again center dialog on screenbottom"; + this->move( ScreenSize.width()/2 - this->width()/2, + ScreenSize.height() - this->height()); + + // Set the mouse pointer in the middle of the screen + QCursor::setPos(monitorMode->width/2, monitorMode->height/2); } // Delete the backup for ( CrtcMap::iterator it = backup.begin(); - it != backup.end(); ++it ) { - delete[] it.value()->outputs; - delete it.value(); + it != backup.end(); ++it ) { + delete[] it.value()->outputs; + delete it.value(); } - // Intenal settings changed. Update! - updateScreenResources(); + */ } -bool Widget::cloneMode() -{ - bool cloneMode = true; - - for (CrtcMap::iterator it = _crtcMap.begin(); it != _crtcMap.end(); it++) { - XRRCrtcInfo* crtc = it.value(); - - // check if x starts on upper left corner - if (crtc->x != 0 || crtc->y != 0) { - cloneMode = false; - } - - qDebug() << "width: " << crtc->width - << "height: " << crtc->height - << "x: " << crtc->x - << "y: " << crtc->y - << "mode: " << crtc->mode; - } - - return cloneMode; -} //////////////////////////////////////////////////////////////////////////////// diff --git a/src/widget.h b/src/widget.h index 6e8d7d6..05e27a6 100644 --- a/src/widget.h +++ b/src/widget.h @@ -1,57 +1,32 @@ -// Copyright 2013, University of Freiburg, -// Author: Manuel Schneider <ms1144> - #ifndef WIDGET_H #define WIDGET_H -#include <QtWidgets> // for Qt5 +#include <QWidget> // for Qt5 #include <QDebug> -#include <X11/Xlib.h> -#include <X11/extensions/Xrandr.h> - namespace Ui { class Widget; } class Widget : public QWidget { - typedef QSet<RRMode> ModeSet; - typedef QSet<RRCrtc> CrtcSet; - typedef QSet<RROutput> OutputSet; - - typedef QList<RRMode> ModeList; - typedef QList<RRCrtc> CrtcList; - typedef QList<RROutput> OutputList; + Q_OBJECT - typedef QMap<RRMode, XRRModeInfo*> ModeMap; - typedef QMap<RRCrtc, XRRCrtcInfo*> CrtcMap; - typedef QMap<RROutput, XRROutputInfo*> OutputMap; - - Q_OBJECT - public: + explicit Widget(QWidget *parent = nullptr); + ~Widget(); - explicit Widget(bool testMode, QWidget *parent = 0); - ~Widget(); +protected: + virtual void showEvent(QShowEvent *event); - private slots: - void handleButton(); - void bringToTopTimer(); +private slots: + void handleButton(); + void bringToTopTimer(); private: - void timeout(); - void updateScreenResources(); - bool cloneMode(); - - Ui::Widget * _ui; - Display* _display; - XRRScreenResources* _screenResources; - ModeMap _modeMap; - CrtcMap _crtcMap; - OutputMap _outputMap; - OutputList _connectedOutputList; - RROutput _beamer, _monitor; + Ui::Widget *_ui; + + void initControls(); }; #endif // WIDGET_H diff --git a/src/widget.ui b/src/widget.ui index 6086eae..2287936 100644 --- a/src/widget.ui +++ b/src/widget.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>600</width> - <height>480</height> + <width>796</width> + <height>309</height> </rect> </property> <property name="sizePolicy"> @@ -16,17 +16,203 @@ <verstretch>0</verstretch> </sizepolicy> </property> - <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> <layout class="QHBoxLayout" name="horizontalLayout"> <item> - <widget class="QComboBox" name="comboBox"/> - </item> - <item> - <widget class="QPushButton" name="pushButton"> - <property name="text"> - <string>Apply</string> + <widget class="QTabWidget" name="tabWidget"> + <property name="currentIndex"> + <number>2</number> </property> + <widget class="QWidget" name="tabClone"> + <attribute name="title"> + <string>Clone</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>In this mode, all connected outputs will display the same image. This is most useful if there is one monitor and one projector currently connected to the computer.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Please select the mode you want to apply. The mode that has been detected as the potentially best mode is highlighted.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="cboCloneResolution"/> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="btnCloneApply"> + <property name="text"> + <string>Apply</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="QWidget" name="tabDual"> + <property name="styleSheet"> + <string notr="true">#lblDualLeft, #lblDualRight { +border-radius: 3px; +border: 2px solid black; +}</string> + </property> + <attribute name="title"> + <string>Dual Screen</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout4" stretch="1,0"> + <item> + <layout class="QHBoxLayout" name="dualContainer"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QLabel" name="lblDualLeft"> + <property name="text"> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="cboDualLeft"/> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="btnDualSwap"> + <property name="text"> + <string><- Swap -></string> + </property> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QLabel" name="lblDualRight"> + <property name="text"> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="margin"> + <number>0</number> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="cboDualRight"/> + </item> + </layout> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="btnDualApply"> + <property name="text"> + <string>Apply</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="QWidget" name="tabAdvanced"> + <attribute name="title"> + <string>Advanced Setup</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout9" stretch="1,0,0"> + <item> + <layout class="QHBoxLayout" name="advancedContainer"/> + </item> + <item> + <layout class="QGridLayout" name="advancedCombos"/> + </item> + <item> + <layout class="QHBoxLayout" name="advancedButtons"> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="btnAdvancedApply"> + <property name="text"> + <string>Apply</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> </widget> </item> </layout> @@ -1,513 +1,621 @@ #include "x.h" +#include "cvt.h" #include <QDebug> -namespace X -{ + /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// - Screen * Screen::_instance = NULL; - - Screen::Screen() - { - // Get initial data (to be freed) - _display = XOpenDisplay(NULL); - _screenResources = XRRGetScreenResourcesCurrent(_display, DefaultRootWindow(_display)); - - /* Get informations about Xserver */ - updateScreenResources(); - - } - - //___________________________________________________________________________ - void Screen::updateScreenResources() - { - // Create the modemap - _modeMap.clear(); - for (int i = 0; i < _screenResources->nmode; ++i) - { - _modeMap.insert( - _screenResources->modes[i].id, - Mode(&_screenResources->modes[i]) - ); - } - - // Create crtcMap - _crtcMap.clear(); - for (int i = 0; i < _screenResources->ncrtc; ++i) - { - _crtcMap.insert( - _screenResources->crtcs[i], - Crtc(_screenResources->crtcs[i],this) - ); - } - - // Create outputMap - _outputMap.clear(); - for (int i = 0; i < _screenResources->noutput; ++i) - { - _outputMap.insert( - _screenResources->outputs[i], - new Output(_screenResources->outputs[i], this) - ); - } - - // Create connectedOutputMap - _connectedOutputList.clear(); - for (OutputMap::iterator it = _outputMap.begin(); - it != _outputMap.end(); ++it) - if ( (*it)->isConnected() ) - _connectedOutputList.push_back((*it)->_id); - } - - //___________________________________________________________________________ - void Screen::createMode( - unsigned int resX, - unsigned int resY, - unsigned long dotClock, - unsigned int hSyncStart, - unsigned int hSyncEnd, - unsigned int hTotal, - unsigned int vSyncStart, - unsigned int vSyncEnd, - unsigned int vTotal, - QString name) - { - XRRModeInfo m; - m.width = resX; - m.height = resY; - m.dotClock = dotClock; - m.hSyncStart= hSyncStart; - m.hSyncEnd = hSyncEnd; - m.hTotal = hTotal; - m.hSkew = 0; - m.vSyncStart= vSyncStart; - m.vSyncEnd = vSyncEnd; - m.vTotal = vTotal; - QByteArray ba = name.toLocal8Bit(); - m.name = ba.data(); - - XRRCreateMode(const_cast<Display*>(X::Screen::inst()->display()), DefaultRootWindow(X::Screen::inst()->display()), &m); - //XRRAddOutputMode(); - } - - //___________________________________________________________________________ - Screen::~Screen() - { - XCloseDisplay(_display); - XRRFreeScreenResources(_screenResources); - for (int i = 0; i < _outputMap.size(); ++i) - delete _outputMap[i]; - } - - -// //_________________________________________________________________________ -//// int Screen::applyChanges() -//// { -//// // First make backup to restore in case of an error or user interaction - -//// // Calculate screensize -//// QRect screenSize; -//// for (CrtcMap::iterator i = _crtcMap.begin(); i != _crtcMap.end(); ++i) -//// screenSize = screenSize.united(i->getRect()); - -//// /* values from xrandr */ -//// float dpi = (25.4 * DisplayHeight(_display, 0)) / -//// DisplayHeightMM(_display, 0); -//// int widthMM = (int) ((25.4 * screenSize.width()) / dpi); -//// int heightMM = (int) ((25.4 * screenSize.height()) / dpi); - -//// // Set screensize -//// XRRSetScreenSize(_display, DefaultRootWindow(_display), -//// screenSize.width(), -//// screenSize.height(), -//// widthMM, heightMM); - -//// // Apply changes of each crtc -//// // Stupid applying here, sanitychecks have to be done before -//// for (CrtcMap::iterator i = _crtcMap.begin(); i != _crtcMap.end(); ++i) -//// { -//// if ( i->applyChanges() ) -//// { -//// revertChanges(); -//// return EXIT_FAILURE; -//// } -//// } -//// return EXIT_SUCCESS; -//// } - - -//// //_________________________________________________________________________ -//// void Screen::revertChanges() -//// { -//// // TODO -//// } - -//// OutputList Screen::getConnectedOutputs() const -//// { -//// OutputList result; -//// for (OutputMap::const_iterator it = _outputMap.begin(); -//// it != _outputMap.end(); ++it) -//// if (it->isConnected()) -//// result.push_back(it->_id); -//// return result; -//// } - -///////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////// - - XElement::XElement(XID xid) - : _id(xid), _validity(false) {} - -///////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////// - - Mode::Mode(XRRModeInfo *info) - { - if ( info == NULL ) - return; - _id = info->id; - _dotClock = info->dotClock; - _hSyncStart = info->hSyncStart; - _hSyncEnd = info->hSyncEnd; - _hTotal = info->hTotal; - _hSkew = info->hSkew; - _vSyncStart = info->vSyncStart; - _vSyncEnd = info->vSyncEnd; - _vTotal = info->vTotal; - _name = QString(info->name); - _modeFlags = info->modeFlags; - _resolution.setWidth(info->width); - _resolution.setHeight(info->height); - _validity = true; - // rate = ((float) info->dotClock / ((float) info->hTotal * (float) info->vTotal)); - - qDebug() << "Mode: " << _id << _resolution.width() << _resolution.height(); - } - -///////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////// - - Crtc::Crtc(XID id, Screen * parent) - : XElement(id), _parent(parent) - { - // Get the information from XRROutputInfo - XRRCrtcInfo * info = XRRGetCrtcInfo( - const_cast<Display*>(_parent->display()), - const_cast<XRRScreenResources*>(_parent->screenResources()), - id); - - // Leave invalid if XID not existent - if ( !info ){ - return; - } - - _timestamp = info->timestamp; - _crtcRect = QRect( - info->x, - info->y, - info->width, - info->height); - _mode = info->mode; - - for (int i = 0; i < info->noutput; ++i) - _outputs.append(info->outputs[i]); - - for (int i = 0; i < info->npossible; ++i) - _possible.append(info->possible[i]); - - _rotation = info->rotation; - _rotations = info->rotations; - _validity = true; - XRRFreeCrtcInfo(info); - - qDebug() << "Crtc: " << _id << _mode << _outputs - << _crtcRect; - } - -//// int Crtc::applyChanges() -//// { -//// return XRRSetCrtcConfig(_parent->display, -//// _parent->screenResources, -//// _id, -//// _timestamp, -//// _crtcRect.x(), -//// _crtcRect.y(), -//// _mode, -//// _rotation, -//// _outputs.toVector().data(), -//// _outputs.size() -//// ); -//// } - -///////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////// - - Output::Output(XID id, Screen * parent) - : XElement(id), _parent(parent), _hasReliableEDID(false) - { - // Get the information from XRROutputInfo - XRROutputInfo* info = XRRGetOutputInfo( - const_cast<Display*>(_parent->display()), - const_cast<XRRScreenResources*>(_parent->screenResources()), - id); - - // Leave invalid if XID not existent - if ( !info ){ - return; - } - - _timestamp = info->timestamp; - _crtc = info->crtc; - _name = QString(info->name); - _metricDimension.setWidth(info->mm_width); - _metricDimension.setHeight(info->mm_height); - _connection = (State)info->connection; - //subpixel_order = info->subpixel_order; - for (int i = 0; i < info->ncrtc; ++i) - _crtcs.append(info->crtcs[i]); - for (int i = 0; i < info->nclone; ++i) - _clones.append(info->clones[i]); - - // List the supported modes and extract preferred - // This is the point where creating the modemap before the outputmap - // gets a contition. - for (int i = 0; i < info->nmode; ++i){ - _modes.insert(info->modes[i]); - if (i < info->npreferred) - _preferred = i; - } - XRRFreeOutputInfo(info); - - // Check if this is a beamer - _isProjector = _metricDimension.isEmpty(); - - // Maybe obsolete, since no preferred mode means no EDID. - // // EDID = ?; - int nprop; - Atom *props = XRRListOutputProperties( - const_cast<Display*>(_parent->display()), - _id, - &nprop); - - for (int i = 0; i < nprop; ++i) - { - char *atom_name = XGetAtomName ( - const_cast<Display*>(_parent->display()), - props[i]); - if ( strcmp (atom_name, "EDID") == 0) - { -// // Print Stuff -// unsigned long nitems, bytes_after; -// unsigned char *prop; -// int actual_format; -// Atom actual_type; -// int bytes_per_item; - -// XRRGetOutputProperty (dpy, ID, props[i], -// 0, 100, False, False, -// AnyPropertyType, -// &actual_type, &actual_format, -// &nitems, &bytes_after, &prop); - -// XRRPropertyInfo *propinfo = XRRQueryOutputProperty(dpy, ID, props[i]); -// bytes_per_item = actual_format / 8; - -// fprintf (stderr, "\t%s: ", atom_name); -// for (unsigned int k = 0; k < nitems; k++) -// { -// if (k != 0) -// { -// if ((k % 16) == 0) -// { -// fprintf (stderr, "\n\t\t"); -// } -// } -// const uint8_t *val = prop + (k * bytes_per_item); -// fprintf (stderr, "%d02", *val); -// } -// free(propinfo); - _hasReliableEDID = true; - } - } - free(props); - - - qDebug() << "Output: " << _id << _name << _crtc - << _metricDimension << _clones << _modes; - } - -// //_________________________________________________________________________ -//// int Screen::Output::changeMode(XID mode) -//// { -//// // Check if this mode is supported -//// if ( ! _modes.contains(mode) ) -//// return 1; - -//// // Check if this output is connected -//// if ( _connection != Output::Connected ) -//// return 2; - -//// // If this output is NOT conected to a crtc -//// if ( ! isActive() ) -//// { -//// // Try to find a unconnected crtc which -//// // this output can be connected to -//// for (CrtcList::iterator i = _crtcs.begin(); ; ++i) -//// { -//// // If this search reches end no appropriate -//// // crtc has been found -//// if ( i == _crtcs.end() ) -//// return 3; - -//// // If a free crtc was found connect and apply mode -//// if ( _parent->_crtcMap[*i].getConnectedOutputs().empty() ) -//// { -//// _parent->_crtcMap[*i].connect(this, mode); -//// break; -//// } -//// } -//// } -//// //If it is already connected apply mode to the crtc -//// // TODO(Manuel):continue -//// return 0; -//// } - -//// //_________________________________________________________________________ -//// int Output::changePos() -//// { -//// return 1; -//// } -///////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////// -} - - - - - - +enum class Projector { + No, + Yes, + Maybe +}; + +struct OutputInfo { + OutputInfo(RROutput id, XRROutputInfo *output, XRRCrtcInfo *crtc, XRRModeInfo *mode) + : output(output), crtc(crtc), mode(mode), id(id), position(-1), isProjector(Projector::No) {} + ~OutputInfo() { + XRRFreeOutputInfo(output); + } + XRROutputInfo* output; + XRRCrtcInfo* crtc; + XRRModeInfo* mode; + RROutput id; + QString modelName; + QString outputName; + int position; + Projector isProjector; +}; + +ScreenSetup * ScreenSetup::_instance = nullptr; + +ScreenSetup::ScreenSetup() : _screenResources(nullptr) +{ + // Get initial data (to be freed) + _display = XOpenDisplay(nullptr); + if (_display == nullptr) { + qFatal("Cannot open display"); + ::exit(1); + } + _EDID_ATOM = XInternAtom(_display, RR_PROPERTY_RANDR_EDID, False); + /* Get informations about Xserver */ + updateScreenResources(); +} +void ScreenSetup::freeResources() +{ + // Clear the modemap (nothing to be freed, stored in screenResources) + _modeMap.clear(); + _resolutions.clear(); + + // Clear output info + for (OutputInfo *output : _outputMap.values()) { + delete output; + } + _outputMap.clear(); + + // Clear crtc info + for (XRRCrtcInfo *info : _crtcMap.values()) { + XRRFreeCrtcInfo(info); + } + _crtcMap.clear(); + + // Release resources + if (_screenResources != nullptr) { + XRRFreeScreenResources(_screenResources); + _screenResources = nullptr; + } + XRRFreeScreenResources(_screenResources); +} +static bool xRectLessThan(const OutputInfo* const &a, const OutputInfo* const &b) +{ + return a->crtc->x < b->crtc->x; +} +static double toVertRefresh(const XRRModeInfo *mode) +{ + if (mode->hTotal > 0 && mode->vTotal > 0) + return (double(mode->dotClock) / (double(mode->hTotal) * double(mode->vTotal))); + return 0; +} +/** + * Check list of known model names that falsely report a screen size or similar + */ +static QStringList initProjectorList() +{ + QStringList list; + list << "AT-HDVS-RX"; // Switchbox + // TODO: Load from file + return list; +} +static bool isProjectorName(const QString &name) +{ + static QStringList projectors = initProjectorList(); + return projectors.contains(name); +} +//___________________________________________________________________________ +void ScreenSetup::updateScreenResources() +{ + freeResources(); + _screenResources = XRRGetScreenResourcesCurrent(_display, DefaultRootWindow(_display)); + // Create the modemap + qDebug() << "Modes"; + for (int i = 0; i < _screenResources->nmode; ++i) { + _modeMap.insert( + _screenResources->modes[i].id, + &_screenResources->modes[i]); + qDebug() << _screenResources->modes[i].id << "\t" + << _screenResources->modes[i].width << "x" + << _screenResources->modes[i].height; + } + + /* + int num = 0; + XRRMonitorInfo *mi = XRRGetMonitors(_display, DefaultRootWindow(_display), True, &num); + for (int i = 0; i < num; ++i) { + char *name = XGetAtomName(_display, mi [i].name); + if (name == nullptr) + continue; + qDebug() << "some monitor with name" << name << mi[i].primary; + XFree(name); + } + */ + + // Create crtcMap + qDebug() << "CRTCs"; + for (int i = 0; i < _screenResources->ncrtc; ++i) { + XRRCrtcInfo * info = XRRGetCrtcInfo( + _display, + _screenResources, + _screenResources->crtcs[i]); + if (info == nullptr) { + qDebug() << "Error getting CRTC info for crtc" << i; + continue; + } + qDebug() << _screenResources->crtcs[i] << "-- Outputs:" << info->noutput << "Possible:" << info->npossible; + _crtcMap.insert(_screenResources->crtcs[i], info); + } + + // Create outputmap and connectedOutputMap + qDebug() << "Outputs"; + QHash<RROutput, QString> tempMap; + QMap<Projector, int> typeCount; + for (int i = 0; i < _screenResources->noutput; ++i) { + XRROutputInfo* info = XRRGetOutputInfo( + _display, + _screenResources, + _screenResources->outputs[i]); + if (info == nullptr) { + qDebug() << "Error getting info for output" << i; + continue; + } + const QString outputName = QString::fromLocal8Bit(info->name, info->nameLen); + tempMap.insert(_screenResources->outputs[i], outputName); + if (info->connection != RR_Connected) { + qDebug() << "Ignoring disconnected output" << outputName; + XRRFreeOutputInfo(info); + continue; + } + if (!_crtcMap.contains(info->crtc)) { + qDebug() << "Have output" << outputName << "with no known crtc"; + XRRFreeOutputInfo(info); + continue; + } + XRRCrtcInfo *crtc = _crtcMap.value(info->crtc); + if (!_modeMap.contains(crtc->mode)) { + qDebug() << "Have output" << outputName << " with crtc with no known mode"; + XRRFreeOutputInfo(info); + continue; + } + XRRModeInfo *mode = _modeMap.value(crtc->mode); + OutputInfo *oi = new OutputInfo(_screenResources->outputs[i], info, crtc, mode); + oi->outputName = outputName; + if (!this->readEdid(oi)) { // We have no EDID - take it as an indicator that there's a dumb output split/switch box + oi->isProjector = Projector::Maybe; + } else if ((info->mm_height == 0 && info->mm_width == 0) || isProjectorName(oi->modelName)) { + oi->isProjector = Projector::Yes; // Screens with size 0x0 are projectors by convention + } else if (info->mm_width > 500 && info->mm_height > 500) { // Big screen - probably should be handled like a projector + oi->isProjector = Projector::Yes; + } else if (info->mm_width > 350 && info->mm_height > 350) { // Somewhere in-between + oi->isProjector = Projector::Maybe; + } + typeCount[oi->isProjector]++; + _outputMap.insert(_screenResources->outputs[i], oi); + qDebug() << "Connected" << outputName << "-- Clones:" << info->nclone << "Modes:" << info->nmode << "Crtcs:" << info->ncrtc << "Preferred:" << info->npreferred; + } + // Final checks for projector + if (typeCount[Projector::Yes] == 0) { + // No definitive projector + if (typeCount[Projector::Maybe] > 0 && typeCount[Projector::No] > 0) { + // Potential projector(s) and normal screen, promote + for (OutputInfo *info : _outputMap) { + if (info->isProjector == Projector::Maybe) { + info->isProjector = Projector::Yes; + break; + } + } + } + } + // Print mappings + for (XRRCrtcInfo *info : _crtcMap) { + qDebug() << info << ":"; + for (int i = 0; i < info->npossible; ++i) { + qDebug() << "Possible:" << tempMap[info->possible[i]]; + } + } + // Determine each screen's position + QList<OutputInfo*> screens = _outputMap.values(); + // Nothing connected? + if (screens.isEmpty()) + return; + qSort(screens.begin(), screens.end(), xRectLessThan); + int endX = -0xffff; + qDebug() << "From left to right"; + for (OutputInfo* output : screens) { + if (output->crtc->x >= endX) { + QSize res(0, 0); + if (_modeMap.contains(output->crtc->mode)) { + auto mode = _modeMap.value(output->crtc->mode); + res = QSize(int(mode->width), int(mode->height)); + } + _resolutions.append(res); + endX = -0xffff; // Reset + } + output->position = _resolutions.size() - 1; + qDebug() << "Screen (" << output->crtc->x << "," << output->crtc->y << ") @" << output->crtc->width << "x" << output->crtc->height << "as screen" << output->position; + if (output->crtc->x + int(output->crtc->width) > endX) { + endX = output->crtc->x + int(output->crtc->width); + } + } + qDebug() << "Loaded."; +} +QHash<QString, int> ScreenSetup::getScreenPositions() const +{ + QHash<QString, int> ret; + for (auto oi : _outputMap) { + ret.insert(oi->outputName, oi->position); + } + return ret; +} +bool ScreenSetup::readEdid(OutputInfo* output) +{ + int numProps = 0; + bool found = false; + Atom* properties = XRRListOutputProperties(_display, output->id, &numProps); + for (int i = 0; i < numProps; ++i) { + if (properties[i] == _EDID_ATOM) { + found = true; + break; + } + } + XFree(properties); + if (!found) + return false; + + unsigned long nitems, bytes_after; + unsigned char *prop; + int actual_format; + Atom actual_type; + bool valid; + + XRRGetOutputProperty(_display, output->id, _EDID_ATOM, + 0, 128, False, False, + AnyPropertyType, + &actual_type, &actual_format, + &nitems, &bytes_after, &prop); + valid = (actual_format == 8 && nitems >= 128); + + if (valid) { + int idx; + for (unsigned char *byte = prop + 54; byte < prop + 126; byte += 18) { + if (byte[0] != 0 || byte[1] != 0 || byte[2] != 0) + continue; // Not a text block + if (byte[3] == 0xfc) { // Serial number + output->modelName = QString::fromLatin1(reinterpret_cast<const char*>(byte) + 5, 13); // It's actually CP-437 but meh + if ((idx = output->modelName.indexOf('\r')) != -1) { + output->modelName.truncate(idx); + } + output->modelName = output->modelName.trimmed(); + qDebug() << "Display name:" << output->modelName; + } + } + } + XFree(prop); + return valid; +} +/** + * Create common modes and add them to all outputs. + * Make sure every output has some variant of the most commonly + * used modes. + */ +void ScreenSetup::initModes() +{ + // First copy typical resolutions to all outputs +#define RES(x,y) (((y) << 16) | (x)) + QSet<quint32> wanted; + wanted << RES(1280, 720) << RES(1280, 800) << RES(1920, 1080); + for (XRRModeInfo *mode : _modeMap) { + if (toVertRefresh(mode) < 58 || toVertRefresh(mode) > 61) + continue; // Play it safe and consider only those for copying that are 60Hz + wanted.remove(RES(mode->width, mode->height)); + // Make sure all outputs got it + for (OutputInfo *info : _outputMap) { + if (getOutputModeForResolution(info->output, mode->width, mode->height)) + continue; + XRRAddOutputMode(_display, info->id, mode->id); + } + } +#undef RES + // Create those that no output supported + for (auto res : wanted) { + unsigned int x = res & 0xffff; + unsigned int y = res >> 16; + createMode(x, y, 60, QString::asprintf("%ux%u", x, y)); + } + if (!wanted.isEmpty()) { + updateScreenResources(); + } + // Finally copy all those the projector supports to other outputs + for (auto key : _outputMap.keys()) { + OutputInfo *oi = _outputMap[key]; + if (oi->isProjector == Projector::Yes) { + copyModesToAll(key, oi->output->nmode); + } + } + updateScreenResources(); +} +XRRModeInfo* ScreenSetup::getPreferredMode(OutputInfo *oi) const +{ + if (oi->output->nmode == 0) { + qDebug() << "getPreferredMode: Output" << oi->outputName << "has no modes!?"; + return nullptr; // WTF!? + } + RRMode mode; + if (oi->output->npreferred > 0) { + mode = oi->output->modes[0]; + } else { + mode = getOutputModeForResolution(oi->output, 1920, 1080); + if (mode == None) { + mode = getOutputModeForResolution(oi->output, 1280, 720); + } + if (mode == None) { + mode = getOutputModeForResolution(oi->output, 1280, 800); + } + if (mode == None) { + mode = oi->output->modes[0]; + } + } + if (!_modeMap.contains(mode)) + return nullptr; + return _modeMap[mode]; +} +QList<QSize> ScreenSetup::getTotalSize(const QList<OutputInfo*> &projectors, const QList<OutputInfo*> &screens) const +{ + const int max = qMax(screens.size(), projectors.size()); + QList<QSize> modes; + for (int i = 0; i < max; ++i) { + XRRModeInfo *mode = nullptr; + if (i < projectors.size()) { + mode = getPreferredMode(projectors.at(i)); + } + if (mode == nullptr && i < screens.size()) { + mode = getPreferredMode(screens.at(i)); + } + if (mode != nullptr) { + modes.append(QSize(int(mode->width), int(mode->height))); + } + } + return modes; +} +static QSize getTotalSizeHorz(const QList<QSize> &list) +{ + QSize ret(0, 0); + for (auto e : list) { + ret.rwidth() += e.width(); + ret.rheight() = qMax(ret.height(), e.height()); + } + return ret; +} +void ScreenSetup::setOutputResolution(OutputInfo *oi, int x, int y, const QSize &size, bool dryRun) +{ + RRMode mode = getOutputModeForResolution(oi->output, + static_cast<unsigned int>(size.width()), static_cast<unsigned int>(size.height())); + if (mode == None) { + if (oi->output->nmode == 0) + return; + qDebug() << oi->outputName << "doesn't support" << size << "- falling back to its default"; + mode = oi->output->modes[0]; + } + if (!dryRun) { + XRRSetCrtcConfig(_display, + _screenResources, + oi->output->crtc, + CurrentTime, + x, y, mode, + RR_Rotate_0, + &oi->id, 1); + } + qDebug() << "Set" << oi->outputName << "to" << _modeMap[mode]->width << "x" << _modeMap[mode]->height << "-- offset" << x << "/" << y; +} -//// qDebug() << XRRSetCrtcConfig(display, -//// screenResources, -//// screenResources->crtcs[1], -//// CurrentTime, -//// 0, 0, -//// 597, -//// RR_Rotate_0, -//// &(screenResources->outputs[3]), -//// 1); -//// qDebug() << XRRSetCrtcConfig(display, -//// screenResources, -//// screenResources->crtcs[0], -//// CurrentTime, -//// 1920, 0, -//// 586, -//// RR_Rotate_0, -//// &(screenResources->outputs[1]), -//// 1); -//// HOLY!! -//// XRRSetCrtcConfig(display, -//// screenResources, -//// screenResources->crtcs[1], -//// CurrentTime, -//// 0, 0, -//// 587, -//// RR_Rotate_0, -//// &screenResources->outputs[1], -//// 1); +ScreenMode ScreenSetup::getCurrentMode() +{ + if (_outputMap.size() == 1) + return ScreenMode::Single; + if (_outputMap.size() > 2) + return ScreenMode::Advanced; + for (auto oi : _outputMap) { + if (oi->crtc->x != 0 || oi->crtc->y != 0) + return ScreenMode::Dual; + } + return ScreenMode::Clone; +} +ScreenMode ScreenSetup::setDefaultMode(bool dryRun) +{ + if (_outputMap.size() == 1) // Only one output exists, do nothing + return ScreenMode::Single; + QMap<QString, OutputInfo*> screenMap; + QMap<QString, OutputInfo*> projectorMap; + for (auto o : _outputMap) { + qDebug() << o->outputName << quint32(o->isProjector); + if (o->isProjector == Projector::Yes) { + projectorMap.insert(o->outputName, o); + } else { + screenMap.insert(o->outputName, o); + } + } + auto projectors = projectorMap.values(); + auto screens = screenMap.values(); + qDebug() << projectors.size() << "projectors," << screens.size() << "screens."; + QList<QSize> outputSizes = getTotalSize(projectors, screens); + if (outputSizes.isEmpty()) + return ScreenMode::Advanced; // Dunno lol + QSize screenSize = getTotalSizeHorz(outputSizes); + if (!dryRun) { + // Disconnect everything + for (auto crtc : _crtcMap.keys()) { + XRRSetCrtcConfig(_display, _screenResources, crtc, CurrentTime, + 0, 0, None, RR_Rotate_0, nullptr, 0); + } + // Set new screen size + XRRSetScreenSize(_display, DefaultRootWindow(_display), + screenSize.width(), screenSize.height(), + int(25.4 * screenSize.width() / 96.0), // standard dpi that X uses + int(25.4 * screenSize.height() / 96.0)); // standard dpi that X uses + } + + qDebug() << "Virtual size:" << screenSize << "with" << outputSizes.size() << "different screens."; + + int offset = 0; + for (int i = 0; i < outputSizes.size(); ++i) { + const QSize &size = outputSizes.at(i); + if (i < projectors.size()) { + setOutputResolution(projectors.at(i), offset, 0, size, dryRun); + } + if (i < screens.size()) { + setOutputResolution(screens.at(i), offset, 0, size, dryRun); + } + offset += size.width(); + } + XSync(_display, False); + updateScreenResources(); // Re-Read + if (outputSizes.size() == 1) // One output size, at least 2 outputs in total -- clone mode + return ScreenMode::Clone; + if (outputSizes.size() == 2 && _outputMap.size() == 2) // Two outputs, two sizes -- extended + return ScreenMode::Dual; + return ScreenMode::Advanced; // Must be more than 2 outputs -> something more involved +} +/** + * Copy first "num" modes from output to all other outputs if they + * dont have a suitable mode yet. + */ +void ScreenSetup::copyModesToAll(RROutput sourceId, int num) +{ + const XRROutputInfo *outputInfo = _outputMap[sourceId]->output; + for (int i = 0; i < num; ++i) { + if (!_modeMap.contains(outputInfo->modes[i])) { + qDebug() << "BUG: Mode" << outputInfo->modes[i] << "not found in copyModesToAll"; + continue; + } + const XRRModeInfo *mode = _modeMap[outputInfo->modes[i]]; + for (auto other : _outputMap.keys()) { + if (other == sourceId) + continue; + const XRROutputInfo *otherInfo = _outputMap[other]->output; + if (getOutputModeForResolution(otherInfo, mode->width, mode->height) != None) + continue; + XRRAddOutputMode(_display, other, outputInfo->modes[i]); + } + } +} +/** + * Get the RRMode for the specified output matching the given resolution. + * If multiple modes match the given resolution, the function will + * return the first matching preferred mode. If no preferred + * mode matches, the non-preferred mode with the highest refresh rate + * will be returned. + * Otherwise, None will be returned. + */ +RRMode ScreenSetup::getOutputModeForResolution(const XRROutputInfo *output, unsigned int width, unsigned int height) const +{ + XRRModeInfo *retval = nullptr; + for (int i = 0; i < output->nmode; ++i) { + if (!_modeMap.contains(output->modes[i])) + continue; + XRRModeInfo *info = _modeMap[output->modes[i]]; + if (info->width == width && info->height == height) { + if (i < output->npreferred) + return output->modes[i]; + if (retval == nullptr || retval->dotClock < info->dotClock) { + retval = info; + } + } + } + return retval == nullptr ? None : retval->id; +} +//___________________________________________________________________________ +bool ScreenSetup::createMode(unsigned int resX, unsigned int resY, float refresh, QString name) +{ + QByteArray ba = name.toLocal8Bit(); + mode *mode = vert_refresh(int(resX), int(resY), refresh, 0, 0, 0); + if (mode == nullptr) + return false; + XRRModeInfo m; + m.width = static_cast<unsigned int>(mode->hr); + m.height = static_cast<unsigned int>(mode->vr); + m.dotClock = static_cast<unsigned long>(mode->pclk) * 1000ul * 1000ul; + m.hSyncStart= static_cast<unsigned int>(mode->hss); + m.hSyncEnd = static_cast<unsigned int>(mode->hse); + m.hTotal = static_cast<unsigned int>(mode->hfl); + m.hSkew = 0; + m.vSyncStart= static_cast<unsigned int>(mode->vss); + m.vSyncEnd = static_cast<unsigned int>(mode->vse); + m.vTotal = static_cast<unsigned int>(mode->vfl); + m.id = 0; + m.name = ba.data(); + m.nameLength = static_cast<unsigned int>(ba.length()); + free(mode); + + for (XRRModeInfo *mode : _modeMap) { + if (mode->width == m.width && mode->height == m.height && mode->dotClock == m.dotClock) + return true; // Already exists, return true? + } + + RRMode xid = XRRCreateMode(_display, DefaultRootWindow(_display), &m); + qDebug() << "Return value of create was" << xid; + // Immediately add to all screens + for (OutputInfo *info : _outputMap) { + XRRAddOutputMode(_display, info->id, xid); + } + return true; +} +//___________________________________________________________________________ +ScreenSetup::~ScreenSetup() +{ + freeResources(); + XCloseDisplay(_display); +} +bool ScreenSetup::applyChanges() +{ + return true; +} +void ScreenSetup::revertChanges() +{ +} -/////////////////////////////// DBEUG //// DEBUG ////////////////////////////////// -//// //SCREEN -//// qDebug() << "ScreenResources: "; -//// qDebug() << "Count of crtcs: " << screenResources->ncrtc; -//// qDebug() << "Count of outputs: " << screenResources->noutput; -//// qDebug() << "Count of modes: " << screenResources->nmode; +static bool modeBiggerThan(const QPair<quint32, quint32> &a, const QPair<quint32, quint32> &b) +{ + if (a.first > b.first) + return true; + if (a.first == b.first) + return a.second > b.second; + return false; +} -//// //MODES -//// for (int i = 0; i < screenResources->nmode; ++i) -//// { -//// qDebug() << screenResources->modes[i].id -//// << screenResources->modes[i].width -//// << screenResources->modes[i].height -////// << screenResources->modes[i].dotClock -////// << screenResources->modes[i].hSyncStart -////// << screenResources->modes[i].hSyncEnd -////// << screenResources->modes[i].hTotal -////// << screenResources->modes[i].hSkew -////// << screenResources->modes[i].vSyncStart -////// << screenResources->modes[i].vSyncEnd -////// << screenResources->modes[i].vTotal -////// << screenResources->modes[i].nameLength -//// << screenResources->modes[i].name; -//// } +QList<QPair<quint32, quint32>> ScreenSetup::getCommonModes() const +{ + QHash<QPair<quint32, quint32>, QSet<RROutput>> matches; + for (auto oi : _outputMap) { + for (int i = 0; i < oi->output->nmode; ++i) { + if (!_modeMap.contains(oi->output->modes[i])) + continue; + const auto mode = _modeMap[oi->output->modes[i]]; + const QPair<quint32, quint32> pair = qMakePair(mode->width, mode->height); + matches[pair].insert(oi->id); + } + } + QList<QPair<quint32, quint32>> ret; + for (auto it = matches.begin(); it != matches.end(); ++it) { + if (it.value().size() == _outputMap.size()) { + ret.append(it.key()); + } + } + qSort(ret.begin(), ret.end(), modeBiggerThan); + return ret; +} -//// //CRTCS -//// for (int j = 0; j < screenResources->ncrtc; ++j) -//// { -//// XRRCrtcInfo *CrtcInfo = XRRGetCrtcInfo(display, screenResources, screenResources->crtcs[j]); -//// qDebug() << "\n-------- CrtcInfo"; -//// qDebug() << "timestamp: " << CrtcInfo->timestamp; -//// qDebug() << "x: " << CrtcInfo->x; -//// qDebug() << "y: " << CrtcInfo->y; -//// qDebug() << "width: " << CrtcInfo->width; -//// qDebug() << "height: " << CrtcInfo->height; -//// qDebug() << "rotation: " << CrtcInfo->rotation; -//// qDebug() << "noutput: " << CrtcInfo->noutput; -//// qDebug() << "rotations: " << CrtcInfo->rotations; -//// qDebug() << "npossible: " << CrtcInfo->npossible; -//// XRRFreeCrtcInfo(CrtcInfo); -//// } -//// // OUTPUTS -//// for (int nOut = 0; nOut < screenResources->noutput; ++nOut) -//// { -//// XRROutputInfo *OutputInfo = XRRGetOutputInfo (display, screenResources, screenResources->outputs[nOut]); -////// if (OutputInfo->connection == RR_Connected) { -//// qDebug() << "\n--- Output " << nOut; -//// qDebug() << "name " << OutputInfo->name; -//// qDebug() << "mm_width " << OutputInfo->mm_width; -//// qDebug() << "mm_height " << OutputInfo->mm_height; -//// qDebug() << "ncrtc " << OutputInfo->ncrtc; -//// qDebug() << "nclone " << OutputInfo->nclone; -//// qDebug() << "nmode " << OutputInfo->nmode; -//// qDebug() << "npreferred " << OutputInfo->npreferred; - -//// for (int j = 0; j < OutputInfo->nmode; j++) -//// { -//// qDebug() << "mode" << j << ": " << OutputInfo->modes[j]; -//// } -////// } -//// XRRFreeOutputInfo (OutputInfo); -//// } -/////////////////////////////////////////////////////////////////////////////////// @@ -3,7 +3,7 @@ #include <QDebug> #include <QList> -#include <QMap> +#include <QHash> #include <QString> #include <QRect> #include <QSet> @@ -11,285 +11,68 @@ #include <X11/Xlib.h> #include <X11/extensions/Xrandr.h> -namespace X -{ - class Mode; - class Output; - class Crtc; - - - typedef QSet<RRMode> ModeSet; - typedef QSet<RRCrtc> CrtcSet; - typedef QSet<RROutput> OutputSet; - - typedef QList<RRMode> ModeList; - typedef QList<RRCrtc> CrtcList; - typedef QList<RROutput> OutputList; - - - - /////////////////////////////////////////////////////////////////////////// - - - class Screen - { - friend class Crtc; - friend class Output; - - public: - - typedef QMap<RRMode,Mode> ModeMap; - typedef QMap<RRCrtc,Crtc> CrtcMap; - typedef QMap<RROutput,Output*> OutputMap; - - int applyChanges(); - void createMode( - unsigned int resX, - unsigned int resY, - unsigned long dotClock, - unsigned int hSyncStart, - unsigned int hSyncEnd, - unsigned int hTotal, - unsigned int vSyncStart, - unsigned int vSyncEnd, - unsigned int vTotal, - QString name); - void revertChanges(); - void updateScreenResources(); - - // Getters - inline const Display* display() const {return _display;} - inline const XRRScreenResources* screenResources() const {return _screenResources;} - inline const ModeMap& getModeMap() const {return _modeMap;} - inline const OutputMap& getOutputMap() const {return _outputMap;} - inline const OutputList& getConnectedOutputList() const {return _connectedOutputList;} - - // Singleton - inline static Screen* inst() { - if (_instance == 0) _instance = new Screen(); - return _instance; - } - - private: - Screen(); - ~Screen(); - - static Screen * _instance; - Display* _display; - XRRScreenResources* _screenResources; - ModeMap _modeMap; - CrtcMap _crtcMap; - OutputMap _outputMap; - - OutputList _connectedOutputList; - }; - - /////////////////////////////////////////////////////////////////////////// - - - class XElement - { - public: - XElement(XID = 0); - XID _id; - bool _validity; - inline XID getID() const {return _id;} - inline XID isValid() const {return _validity;} - }; - - - /////////////////////////////////////////////////////////////////////////// - - - class Mode : public XElement - { - public: - Mode(XRRModeInfo* = NULL); - - // Xlib internal stuff - QSize _resolution; - unsigned long _dotClock; - unsigned int _hSyncStart; - unsigned int _hSyncEnd; - unsigned int _hTotal; - unsigned int _hSkew; - unsigned int _vSyncStart; - unsigned int _vSyncEnd; - unsigned int _vTotal; - QString _name; - XRRModeFlags _modeFlags; - }; - - - /////////////////////////////////////////////////////////////////////////// - - - class Output : public XElement - { - friend int Screen::applyChanges(); - friend void Screen::revertChanges(); - - typedef enum _State { - Connected = RR_Connected, - Disconnected = RR_Disconnected, - Unknown = RR_UnknownConnection - } State; - - public: - - Output(XID, Screen*); - - /** Public interface to modify output settings. - * This function is the only interface to the outside, which is able to - * change something in this object. - * @param active Indicates wheter the output shall be on or off. - * @param mode The mode wich is used for the output. - * @param position The position of the topleft corner on the screen. - * @return 0 if the config passed teh sanity checks. - */ - int changeConfiguration(bool active, XID mode, QPoint position); +struct OutputInfo; - inline QString getName() const {return _name;} - inline ModeSet getModeSet() const {return _modes;} - inline RRMode getPreferred() const {return _preferred;} - inline bool isActive() const {return !_crtcs.isEmpty();} - inline bool isConnected() const {return _connection == Connected;} - inline bool isProjector() const {return _isProjector;} - inline bool hasReliableEDID() const {return _hasReliableEDID;} +/////////////////////////////////////////////////////////////////////////// - private: - - Screen *_parent; - - // Indicates when the configuration was last set. - Time _timestamp; - - // The current source CRTC for video data, or Disabled if the - // output is not connected to any CRTC. - RRCrtc _crtc; - - // UTF-8 encoded string designed to be presented to the - // user to indicate which output this is. E.g. "S-Video" or "DVI". - QString _name; - - // 'widthInMillimeters' and 'heightInMillimeters' report the physical - // size of the displayed area. If unknown, or not really fixed (e.g., - // for a projector), these values are both zero. - QSize _metricDimension; - - // Indicates whether the hardware was able to detect a - // device connected to this output. If the hardware cannot determine - // whether something is connected, it will set this to - // UnknownConnection. - State _connection; - -// // Contains the resulting subpixel order of the -// // connected device to allow correct subpixel rendering. -// SubpixelOrder _subpixel_order; - - // The list of CRTCs that this output may be connected to. - // Attempting to connect this output to a different CRTC results in a - // Match error. - CrtcList _crtcs; - - // The list of outputs which may be simultaneously - // connected to the same CRTC along with this output. Attempting to - // connect this output with an output not in this list - // results in a Match error. - OutputList _clones; - - // The list of modes supported by this output. Attempting to - // connect this output to a CRTC not using one of these modes results - // in a Match error. - ModeSet _modes; - - // The first 'num-preferred' modes in 'modes' are preferred by the - // monitor in some way; for fixed-pixel devices, this would generally - // indicate which modes match the resolution of the output device. - RRMode _preferred; - - // Indicates wheter this is a beamer or not. - bool _isProjector; - - // Indicates wheter the output received reliable data over the DDC. - // The Display Data Channel, or DDC, is a collection of protocols for - // digital communication between a computer display and a graphics - // adapter that enable the display to communicate its supported display - // modes to the adapter and that enable the computer host to adjust - // monitor parameters, such as brightness and contrast. - // Extended display identification data (EDID) is a data structure - // provided by a digital display to describe its capabilities to a - // video source. - bool _hasReliableEDID; - }; - - - /////////////////////////////////////////////////////////////////////////// - - - class Crtc : public XElement - { - friend int Screen::applyChanges(); - friend void Screen::revertChanges(); - friend int Output::changeConfiguration(bool active, XID mode, QPoint position); - //friend int Output::changeConfiguration(bool active, XID mode, QPoint position); - - public: - - Crtc(XID, Screen*); - - // Getter - inline const OutputList & getConnectedOutputs( ) { return _outputs; } - inline const QRect getRect() { return _crtcRect; } - - // Setter - - - // Applies the changes made to this crtc -// int applyChanges(); -// void disable(); -// void connect(const Output *, Mode); - - private: - - Screen *_parent; - - // Indicates when the configuration was last set. - Time _timestamp; - - // 'x' and 'y' indicate the position of this CRTC within the screen - // region. They will be set to 0 when the CRTC is disabled. - // 'width' and 'height' indicate the size of the area within the screen - // presented by this CRTC. This may be different than the size of the - // mode due to rotation, the projective transform, and the Border - // property described below. - // They will be set to 0 when the CRTC is disabled. - QRect _crtcRect; - - // Indicates which mode is active, or None indicating that the - // CRTC has been disabled and is not displaying the screen contents. - RRMode _mode; - - // The list of outputs currently connected to this CRTC, - // is empty when the CRTC is disabled. - OutputList _outputs; - - // Lists all of the outputs which may be connected to this CRTC. - OutputList _possible; - - // The active rotation. Set to Rotate_0 when the CRTC is disabled. - Rotation _rotation; - // enum Rotation { - // Normal = RR_Rotate_0, - // Left = RR_Rotate_270, - // Right = RR_Rotate_90, - // UpsideDown = RR_Rotate_180 - // }; +enum class ScreenMode +{ + Single, + Clone, + Dual, + Advanced, +}; - // contains the set of rotations and reflections supported by the CRTC - Rotation _rotations; - }; -} +class ScreenSetup +{ +public: + + typedef QHash<RRMode, XRRModeInfo*> ModeMap; + typedef QHash<RRCrtc, XRRCrtcInfo*> CrtcMap; + typedef QHash<RROutput, OutputInfo*> OutputMap; + typedef QVector<QSize> ResolutionVector; + + void updateScreenResources(); + bool applyChanges(); + void initModes(); + XRRModeInfo* getPreferredMode(OutputInfo *oi) const; + QList<QSize> getTotalSize(const QList<OutputInfo*> &projectors, const QList<OutputInfo*> &screens) const; + void setOutputResolution(OutputInfo *oi, int x, int y, const QSize &size, bool dryRun = false); + ScreenMode getCurrentMode(); + ScreenMode setDefaultMode(bool dryRun = false); + void copyModesToAll(RROutput id, int num); + RRMode getOutputModeForResolution(const XRROutputInfo *output, unsigned int width, unsigned int height) const; + bool createMode(unsigned int resX, unsigned int resY, float refresh, QString name); + void revertChanges(); + QList<QPair<quint32, quint32>> getCommonModes() const; + int getOutputCount() const { return _outputMap.size(); } + QHash<QString, int> getScreenPositions() const; + const ResolutionVector &getVirtualResolutions() const { return _resolutions; } + + // Singleton + inline static ScreenSetup* inst() { + if (_instance == nullptr) _instance = new ScreenSetup(); + return _instance; + } + +private: + ScreenSetup(); + ~ScreenSetup(); + + void freeResources(); + bool readEdid(OutputInfo* output); + + static ScreenSetup * _instance; + Display* _display; + Atom _EDID_ATOM; + XRRScreenResources* _screenResources; + ModeMap _modeMap; + CrtcMap _crtcMap; + OutputMap _outputMap; + ResolutionVector _resolutions; +}; + +/////////////////////////////////////////////////////////////////////////// #endif // XRANDR_H |