diff --git a/configure.ac b/configure.ac index 6c9fe41f..0095e0db 100644 --- a/configure.ac +++ b/configure.ac @@ -173,6 +173,16 @@ if test x"$HAVE_XRANDR" = "x1"; then AC_DEFINE(HAVE_XRANDR) fi +# xinerama +if test -n "$PKG_CONFIG"; then + PKG_CHECK_MODULES(XINERAMA, xinerama, [HAVE_XINERAMA=1], [HAVE_XINERAMA=0]) +fi +if test x"$HAVE_XINERAMA" = "x1"; then + CFLAGS="$CFLAGS $XINERAMA_CFLAGS" + LIBS="$LIBS $XINERAMA_LIBS" + AC_DEFINE(HAVE_XINERAMA) +fi + # Xcursor if test -n "$PKG_CONFIG"; then PKG_CHECK_MODULES(XCURSOR, xcursor, [HAVE_XCURSOR=1], [HAVE_XCURSOR=0]) diff --git a/constants.h b/constants.h index 512a35e6..95857587 100644 --- a/constants.h +++ b/constants.h @@ -95,6 +95,15 @@ enum RDP_NEG_FAILURE_CODE SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER = 6 }; +enum RDP_NEG_REQ_FLAGS +{ + EXTENDED_CLIENT_DATA_SUPPORTED = 1, + DYNVC_GFX_PROTOCOL_SUPPORTED = 2, + NEGRSP_FLAG_RESERVED = 4, + RESTRICTED_ADMIN_MODE_SUPPORTED = 8, + REDIRECTED_AUTHENTICATION_MODE_SUPPORTED = 0x10 +}; + /* MCS PDU codes */ enum MCS_PDU_TYPE { @@ -168,6 +177,7 @@ enum MCS_DPU_REASON #define CS_SECURITY 0xc002 #define CS_NET 0xc003 #define CS_CLUSTER 0xc004 +#define CS_MONITOR 0xc005 #define SEC_TAG_PUBKEY 0x0006 #define SEC_TAG_KEYSIG 0x0008 diff --git a/iso.c b/iso.c index 278f32a2..c5a7c52b 100644 --- a/iso.c +++ b/iso.c @@ -26,13 +26,13 @@ extern RD_BOOL g_encryption_initial; extern RDP_VERSION g_rdp_version; extern RD_BOOL g_use_password_as_pin; -static RD_BOOL g_negotiate_rdp_protocol = True; - extern char *g_sc_csp_name; extern char *g_sc_reader_name; extern char *g_sc_card_name; extern char *g_sc_container_name; +extern int g_num_monitors; +extern RD_BOOL g_extended_data_supported; /* Send a self-contained ISO PDU */ static void @@ -62,7 +62,7 @@ iso_send_connection_request(char *username, uint32 neg_proto) STREAM s; int length = 30 + strlen(username); - if (g_rdp_version >= RDP_V5 && g_negotiate_rdp_protocol) + if (g_rdp_version >= RDP_V5) length += 8; s = tcp_init(length); @@ -83,7 +83,7 @@ iso_send_connection_request(char *username, uint32 neg_proto) out_uint8(s, 0x0d); /* cookie termination string: CR+LF */ out_uint8(s, 0x0a); - if (g_rdp_version >= RDP_V5 && g_negotiate_rdp_protocol) + if (g_rdp_version >= RDP_V5) { /* optional RDP protocol negotiation request for RDPv5 */ out_uint8(s, RDP_NEG_REQ); @@ -223,7 +223,7 @@ iso_connect(char *server, char *username, char *domain, char *password, RD_BOOL is_fastpath; uint8 fastpath_hdr; - g_negotiate_rdp_protocol = True; + RD_BOOL ok_to_reconnect = False; neg_proto = PROTOCOL_SSL; @@ -242,7 +242,7 @@ iso_connect(char *server, char *username, char *domain, char *password, logger(Core, Verbose, "Connecting to server using SSL..."); retry: - *selected_protocol = PROTOCOL_RDP; + *selected_protocol = neg_proto; code = 0; if (!tcp_connect(server)) @@ -267,17 +267,16 @@ iso_connect(char *server, char *username, char *domain, char *password, const char *reason = NULL; uint8 type = 0; + uint8 flags = 0; uint32 data = 0; in_uint8(s, type); - in_uint8s(s, 1); /* skip flags */ + in_uint8(s, flags); /* skip flags */ in_uint8s(s, 2); /* skip length */ in_uint32(s, data); if (type == RDP_NEG_FAILURE) { - RD_BOOL retry_without_neg = False; - switch (data) { case SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER: @@ -285,11 +284,11 @@ iso_connect(char *server, char *username, char *domain, char *password, break; case SSL_NOT_ALLOWED_BY_SERVER: reason = "SSL not allowed by server"; - retry_without_neg = True; + ok_to_reconnect = True; break; case SSL_CERT_NOT_ON_SERVER: reason = "no valid authentication certificate on server"; - retry_without_neg = True; + ok_to_reconnect = True; break; case INCONSISTENT_FLAGS: reason = "inconsistent negotiation flags"; @@ -306,7 +305,7 @@ iso_connect(char *server, char *username, char *domain, char *password, tcp_disconnect(); - if (retry_without_neg) + if (ok_to_reconnect) { if (reason != NULL) { @@ -316,7 +315,9 @@ iso_connect(char *server, char *username, char *domain, char *password, } logger(Core, Notice, "Retrying with plain RDP."); - g_negotiate_rdp_protocol = False; + + neg_proto = PROTOCOL_RDP; + goto retry; } @@ -332,6 +333,20 @@ iso_connect(char *server, char *username, char *domain, char *password, return False; } + if (flags & EXTENDED_CLIENT_DATA_SUPPORTED) { + g_extended_data_supported = True; + logger(Protocol, Debug, "Server supports Extended Client Data"); + } + else { + g_extended_data_supported = False; + logger(Protocol, Debug, "Server does not support Extended Client Data"); + } + + if ((g_num_monitors > 1) && !g_extended_data_supported) { + logger(Protocol, Warning, "Got more than 1 monitor but server does not support Extended Client Data"); + g_num_monitors = 1; + } + /* handle negotiation response */ if (data == PROTOCOL_SSL) { diff --git a/rdesktop.c b/rdesktop.c index e633c21d..83f3c834 100644 --- a/rdesktop.c +++ b/rdesktop.c @@ -144,6 +144,12 @@ RD_BOOL g_pending_resize = False; RD_BOOL g_pending_resize_defer = True; struct timeval g_pending_resize_defer_timer = {0}; +RD_BOOL g_extended_data_supported = False; + +/* According to MS-RDPBCGR 2.2.1.3.6 max value for monitorCount is 16 */ +int g_num_monitors = 0; +RDP_MONITOR g_monitors[MAX_MONITORS]; + #ifdef WITH_RDPSND RD_BOOL g_rdpsnd = False; #endif diff --git a/rdesktop.h b/rdesktop.h index 72983205..2ffe35e0 100644 --- a/rdesktop.h +++ b/rdesktop.h @@ -138,6 +138,8 @@ #define EXRD_WINDOW_CLOSED 62 #define EXRD_UNKNOWN 63 +#define MAX_MONITORS 16 + #define STRNCPY(dst,src,n) { strncpy(dst,src,n-1); dst[n-1] = 0; } #ifndef MIN diff --git a/secure.c b/secure.c index 6443c6e1..4dd97179 100644 --- a/secure.c +++ b/secure.c @@ -59,6 +59,11 @@ uint16 g_server_rdp_version = 0; static int g_sec_encrypt_use_count = 0; static int g_sec_decrypt_use_count = 0; +extern int g_num_monitors; +extern RDP_MONITOR g_monitors[MAX_MONITORS]; + +extern RD_BOOL g_extended_data_supported; + /* * I believe this is based on SSLv3 with the following differences: * MAC algorithm (5.2.3.1) uses only 32-bit length in place of seq_num/type/length fields @@ -407,6 +412,10 @@ sec_out_mcs_connect_initial_pdu(STREAM s, uint32 selected_protocol) if (g_num_channels > 0) length += g_num_channels * 12 + 8; + if ((g_num_monitors > 1) && g_extended_data_supported) { + length += (g_num_monitors * 20) + 12; + } + /* Generic Conference Control (T.124) ConferenceCreateRequest */ out_uint16_be(s, 5); out_uint16_be(s, 0x14); @@ -507,6 +516,33 @@ sec_out_mcs_connect_initial_pdu(STREAM s, uint32 selected_protocol) } } + /* 2.2.1.3.6 Client Monitor Data (TS_UD_CS_MONITOR) */ + if ((g_num_monitors > 1) && g_extended_data_supported) { + int lengthMonitors, n; + + logger(Protocol, Debug, "Informing server about %d monitors\n", g_num_monitors); + out_uint16_le(s, CS_MONITOR); /* User Data Header type */ + + lengthMonitors = 12 + (20 * g_num_monitors); + out_uint16_le(s, lengthMonitors); + out_uint32_le(s, 0); /* flags (unused) */ + out_uint32_le(s, g_num_monitors); /* monitorCount */ + for (n = 0; n < g_num_monitors; n++) { + out_uint32_le(s, g_monitors[n].x); /* left */ + out_uint32_le(s, g_monitors[n].y); /* top */ + out_uint32_le(s, g_monitors[n].x + + g_monitors[n].width-1); /* right */ + out_uint32_le(s, g_monitors[n].y + + g_monitors[n].height-1); /* bottom */ + out_uint32_le(s, g_monitors[n].is_primary ? 1 : 0); /* isPrimary */ + logger(Protocol, Debug, "Setting monitors %d %dx%d at %d,%d rect(l:%d,t:%d,r:%d,b:%d)\n", + n,g_monitors[n].width,g_monitors[n].height,g_monitors[n].x,g_monitors[n].y, + g_monitors[n].x, g_monitors[n].y, + g_monitors[n].x + g_monitors[n].width-1, g_monitors[n].y +g_monitors[n].height-1 + ); + } + } + s_mark_end(s); } diff --git a/types.h b/types.h index 922af8ac..ca1edb5c 100644 --- a/types.h +++ b/types.h @@ -306,6 +306,16 @@ typedef struct fileinfo } FILEINFO; +typedef struct rdp_monitor +{ + int x; + int y; + int width; + int height; + RD_BOOL is_primary; +} RDP_MONITOR; + + typedef RD_BOOL(*str_handle_lines_t) (const char *line, void *data); typedef enum { diff --git a/xwin.c b/xwin.c index 5d5b7c9a..9f13b00c 100644 --- a/xwin.c +++ b/xwin.c @@ -36,6 +36,9 @@ #ifdef HAVE_XRANDR #include #endif +#ifdef HAVE_XINERAMA +#include +#endif #ifdef __APPLE__ #include @@ -68,6 +71,10 @@ extern char g_seamless_spawn_cmd[]; extern int g_server_depth; extern int g_win_button_size; +extern int g_num_monitors; +extern RDP_MONITOR g_monitors[MAX_MONITORS]; + + /* This is a timer used to rate limit actual resizing */ static struct timeval g_resize_timer = {0}; @@ -1884,6 +1891,141 @@ set_wm_client_machine(Display * dpy, Window win) XSetWMClientMachine(dpy, win, &tp); } +#ifdef HAVE_XRANDR +static RD_BOOL +xwin_get_monitors_xrandr(void) +{ + int i, j; + int event_base, error_base; + + XRRCrtcInfo *xrrci = NULL; + XRROutputInfo *info = NULL; + XRRScreenResources *xrrr = NULL; + + if (!XRRQueryExtension (g_display, &event_base, &error_base)) { + logger(GUI, Debug, "%s: XRandR is not supported", __func__); + return False; + } + + xrrr = XRRGetScreenResources(g_display, DefaultRootWindow(g_display)); + if (xrrr == NULL) { + logger(GUI, Warning, "%s: XRRGetScreenResources failed", __func__); + return False; + } + + logger(GUI, Verbose, "%s: Number of outputs = %d", __func__, xrrr->noutput); + + j = 0; + + for (i = 0; i < xrrr->noutput; i++) { + info = XRRGetOutputInfo(g_display, xrrr, xrrr->outputs[i]); + + if (info->connection != RR_Connected) { + XRRFreeOutputInfo(info); + continue; + } + + xrrci = XRRGetCrtcInfo(g_display, xrrr, info->crtc); + + logger(GUI, Verbose, "%d) %s: %dx%d+%d+%d", j, info->name, xrrci->width, xrrci->height, xrrci->x, xrrci->y); + + g_monitors[j].x = xrrci->x; + g_monitors[j].y = xrrci->y; + g_monitors[j].width = xrrci->width; + g_monitors[j].width = (g_monitors[j].width + 3) & ~3; // make sure width is a multiple of 4 + g_monitors[j].height = xrrci->height; + g_monitors[j].is_primary = False; + + if ((xrrci->x == 0) && (xrrci->y == 0)) + g_monitors[j].is_primary = True; + + j++; + + XRRFreeCrtcInfo(xrrci); + XRRFreeOutputInfo(info); + } + + logger(GUI, Verbose, "%s: Number of monitors = %d", __func__, j); + + XRRFreeScreenResources(xrrr); + + g_num_monitors = j; + + return True; +} +#endif + +#ifdef HAVE_XINERAMA +static RD_BOOL +xwin_get_monitors_xinerama(void) +{ + int i, isPrimary; + int major_version, minor_version, ncrtc; + + if (!XineramaQueryExtension(g_display, &major_version, &minor_version)) { + logger(GUI, Debug, "Xinerama is not supported"); + return False; + } + if (!XineramaIsActive(g_display)) { + logger(GUI, Debug, "Xinerama is not active"); + return False; + } + + XineramaScreenInfo *p = XineramaQueryScreens(g_display, &ncrtc); + + if ((p == NULL) || (ncrtc <= 0)) { + logger(GUI, Debug, "XineramaQueryScreens failed to get screen info"); + return False; + } + + g_num_monitors = ncrtc; + + logger(GUI, Verbose, "Number of monitors = %d", g_num_monitors); + + isPrimary = 0; + + for (i = 0; i < g_num_monitors; ++i) { + logger(GUI, Verbose, "%dx%d+%d+%d", p[i].width, p[i].height, p[i].x_org, p[i].y_org); + g_monitors[i].x = p[i].x_org; + g_monitors[i].y = p[i].y_org; + g_monitors[i].width = p[i].width; + g_monitors[i].width = (g_monitors[i].width + 3) & ~3; // make sure width is a multiple of 4 + g_monitors[i].height = p[i].height; + g_monitors[i].is_primary = False; + + if ( (p[i].x_org == 0) && (p[i].y_org == 0)) + isPrimary = i; + } + + g_monitors[isPrimary].is_primary = True; + + XFree(p); + + return True; +} +#endif + +static RD_BOOL +xwin_get_monitors(void) +{ + RD_BOOL bMonitorFound = False; + + memset(g_monitors, 0, sizeof(RDP_MONITOR) * MAX_MONITORS); + +#ifdef HAVE_XRANDR + bMonitorFound = xwin_get_monitors_xrandr(); +#endif + +/* if XRandR is not avaible try Xinerama */ +#ifdef HAVE_XINERAMA + if(!bMonitorFound) + bMonitorFound = xwin_get_monitors_xinerama(); +#endif + + return bMonitorFound; +} + + /* Initialize the UI. This is done once per process. */ RD_BOOL ui_init(void) @@ -1961,6 +2103,8 @@ ui_init(void) seamless_init(); } + xwin_get_monitors(); + return True; } @@ -1984,11 +2128,14 @@ void ui_get_workarea_size(uint32 *width, uint32 *height) { uint32 x, y, w, h; + g_num_monitors = 1; + if (get_current_workarea(&x, &y, &w, &h) == 0) { *width = w; *height = h; g_using_full_workarea = True; + xwin_get_monitors(); } else { @@ -2331,6 +2478,7 @@ xwin_toggle_fullscreen(void) } g_fullscreen = !g_fullscreen; + g_num_monitors = 1; /* What size should the new window have? */ @@ -2341,6 +2489,7 @@ xwin_toggle_fullscreen(void) y = 0; width = WidthOfScreen(g_screen); height = HeightOfScreen(g_screen); + xwin_get_monitors(); } else {