/** * FreeRDP: A Remote Desktop Protocol Implementation * X11 Windows * * Licensed under the Apache License, Version 2.0 (the "License");n * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include "xf_floatbar.h" #include "resource/close.xbm" #include "resource/lock.xbm" #include "resource/unlock.xbm" #include "resource/minimize.xbm" #include "resource/restore.xbm" #define TAG CLIENT_TAG("x11") #define FLOATBAR_HEIGHT 26 #define FLOATBAR_DEFAULT_WIDTH 576 #define FLOATBAR_MIN_WIDTH 200 #define FLOATBAR_BORDER 24 #define FLOATBAR_BUTTON_WIDTH 24 #define FLOATBAR_COLOR_BACKGROUND "RGB:31/6c/a9" #define FLOATBAR_COLOR_BORDER "RGB:75/9a/c8" #define FLOATBAR_COLOR_FOREGROUND "RGB:FF/FF/FF" #ifdef WITH_DEBUG_X11 #define DEBUG_X11(...) WLog_DBG(TAG, __VA_ARGS__) #else #define DEBUG_X11(...) \ do \ { \ } while (0) #endif #define XF_FLOATBAR_MODE_NONE 0 #define XF_FLOATBAR_MODE_DRAGGING 1 #define XF_FLOATBAR_MODE_RESIZE_LEFT 2 #define XF_FLOATBAR_MODE_RESIZE_RIGHT 3 #define XF_FLOATBAR_BUTTON_CLOSE 1 #define XF_FLOATBAR_BUTTON_RESTORE 2 #define XF_FLOATBAR_BUTTON_MINIMIZE 3 #define XF_FLOATBAR_BUTTON_LOCKED 4 typedef BOOL (*OnClick)(xfFloatbar*); typedef struct xf_floatbar_button xfFloatbarButton; struct xf_floatbar { int x; int y; int width; int height; int mode; int last_motion_x_root; int last_motion_y_root; bool locked; xfFloatbarButton* buttons[4]; Window handle; BOOL hasCursor; xfContext* xfc; DWORD flags; BOOL created; Window root_window; char* title; }; struct xf_floatbar_button { int x; int y; int type; bool focus; bool clicked; OnClick onclick; Window handle; }; static xfFloatbarButton* xf_floatbar_new_button(xfFloatbar* floatbar, int type); static BOOL xf_floatbar_button_onclick_close(xfFloatbar* floatbar) { if (!floatbar) return FALSE; return freerdp_abort_connect(floatbar->xfc->context.instance); } static BOOL xf_floatbar_button_onclick_minimize(xfFloatbar* floatbar) { xfContext* xfc; if (!floatbar || !floatbar->xfc) return FALSE; xfc = floatbar->xfc; xf_SetWindowMinimized(xfc, xfc->window); return TRUE; } static BOOL xf_floatbar_button_onclick_restore(xfFloatbar* floatbar) { if (!floatbar) return FALSE; xf_toggle_fullscreen(floatbar->xfc); return TRUE; } static BOOL xf_floatbar_button_onclick_locked(xfFloatbar* floatbar) { if (!floatbar) return FALSE; floatbar->locked = (floatbar->locked) ? FALSE : TRUE; return xf_floatbar_hide_and_show(floatbar); } BOOL xf_floatbar_set_root_y(xfFloatbar* floatbar, int y) { if (!floatbar) return FALSE; floatbar->last_motion_y_root = y; return TRUE; } BOOL xf_floatbar_hide_and_show(xfFloatbar* floatbar) { xfContext* xfc; if (!floatbar || !floatbar->xfc) return FALSE; if (!floatbar->created) return TRUE; xfc = floatbar->xfc; if (!floatbar->locked) { if ((floatbar->mode == XF_FLOATBAR_MODE_NONE) && (floatbar->last_motion_y_root > 10) && (floatbar->y > (FLOATBAR_HEIGHT * -1))) { floatbar->y = floatbar->y - 1; XMoveWindow(xfc->display, floatbar->handle, floatbar->x, floatbar->y); } else if (floatbar->y < 0 && (floatbar->last_motion_y_root < 10)) { floatbar->y = floatbar->y + 1; XMoveWindow(xfc->display, floatbar->handle, floatbar->x, floatbar->y); } } return TRUE; } static BOOL create_floatbar(xfFloatbar* floatbar) { xfContext* xfc; Status status; XWindowAttributes attr; if (floatbar->created) return TRUE; xfc = floatbar->xfc; status = XGetWindowAttributes(xfc->display, floatbar->root_window, &attr); floatbar->x = attr.x + attr.width / 2 - FLOATBAR_DEFAULT_WIDTH / 2; floatbar->y = 0; if (((floatbar->flags & 0x0004) == 0) && !floatbar->locked) floatbar->y = -FLOATBAR_HEIGHT + 1; floatbar->handle = XCreateWindow(xfc->display, floatbar->root_window, floatbar->x, 0, FLOATBAR_DEFAULT_WIDTH, FLOATBAR_HEIGHT, 0, CopyFromParent, InputOutput, CopyFromParent, 0, NULL); floatbar->width = FLOATBAR_DEFAULT_WIDTH; floatbar->height = FLOATBAR_HEIGHT; floatbar->mode = XF_FLOATBAR_MODE_NONE; floatbar->buttons[0] = xf_floatbar_new_button(floatbar, XF_FLOATBAR_BUTTON_CLOSE); floatbar->buttons[1] = xf_floatbar_new_button(floatbar, XF_FLOATBAR_BUTTON_RESTORE); floatbar->buttons[2] = xf_floatbar_new_button(floatbar, XF_FLOATBAR_BUTTON_MINIMIZE); floatbar->buttons[3] = xf_floatbar_new_button(floatbar, XF_FLOATBAR_BUTTON_LOCKED); XSelectInput(xfc->display, floatbar->handle, ExposureMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask | LeaveWindowMask | EnterWindowMask | StructureNotifyMask | PropertyChangeMask); floatbar->created = TRUE; return TRUE; } BOOL xf_floatbar_toggle_fullscreen(xfFloatbar* floatbar, bool fullscreen) { int i, size; bool visible = False; xfContext* xfc; if (!floatbar || !floatbar->xfc) return FALSE; xfc = floatbar->xfc; /* Only visible if enabled */ if (floatbar->flags & 0x0001) { /* Visible if fullscreen and flag visible in fullscreen mode */ visible |= ((floatbar->flags & 0x0010) != 0) && fullscreen; /* Visible if window and flag visible in window mode */ visible |= ((floatbar->flags & 0x0020) != 0) && !fullscreen; } if (visible) { if (!create_floatbar(floatbar)) return FALSE; XMapWindow(xfc->display, floatbar->handle); size = ARRAYSIZE(floatbar->buttons); for (i = 0; i < size; i++) { XMapWindow(xfc->display, floatbar->buttons[i]->handle); } /* If default is hidden (and not sticky) don't show on fullscreen state changes */ if (((floatbar->flags & 0x0004) == 0) && !floatbar->locked) floatbar->y = -FLOATBAR_HEIGHT + 1; xf_floatbar_hide_and_show(floatbar); } else if (floatbar->created) { XUnmapSubwindows(xfc->display, floatbar->handle); XUnmapWindow(xfc->display, floatbar->handle); } return TRUE; } xfFloatbarButton* xf_floatbar_new_button(xfFloatbar* floatbar, int type) { xfFloatbarButton* button; button = (xfFloatbarButton*)calloc(1, sizeof(xfFloatbarButton)); button->type = type; switch (type) { case XF_FLOATBAR_BUTTON_CLOSE: button->x = floatbar->width - FLOATBAR_BORDER - FLOATBAR_BUTTON_WIDTH * type; button->onclick = xf_floatbar_button_onclick_close; break; case XF_FLOATBAR_BUTTON_RESTORE: button->x = floatbar->width - FLOATBAR_BORDER - FLOATBAR_BUTTON_WIDTH * type; button->onclick = xf_floatbar_button_onclick_restore; break; case XF_FLOATBAR_BUTTON_MINIMIZE: button->x = floatbar->width - FLOATBAR_BORDER - FLOATBAR_BUTTON_WIDTH * type; button->onclick = xf_floatbar_button_onclick_minimize; break; case XF_FLOATBAR_BUTTON_LOCKED: button->x = FLOATBAR_BORDER; button->onclick = xf_floatbar_button_onclick_locked; break; default: break; } button->y = 0; button->focus = FALSE; button->handle = XCreateWindow(floatbar->xfc->display, floatbar->handle, button->x, 0, FLOATBAR_BUTTON_WIDTH, FLOATBAR_BUTTON_WIDTH, 0, CopyFromParent, InputOutput, CopyFromParent, 0, NULL); XSelectInput(floatbar->xfc->display, button->handle, ExposureMask | ButtonPressMask | ButtonReleaseMask | FocusChangeMask | LeaveWindowMask | EnterWindowMask | StructureNotifyMask); return button; } xfFloatbar* xf_floatbar_new(xfContext* xfc, Window window, const char* name, DWORD flags) { xfFloatbar* floatbar; /* Floatbar not enabled */ if ((flags & 0x0001) == 0) return NULL; if (!xfc) return NULL; /* Force disable with remote app */ if (xfc->remote_app) return NULL; floatbar = (xfFloatbar*)calloc(1, sizeof(xfFloatbar)); if (!floatbar) return NULL; floatbar->title = _strdup(name); if (!floatbar->title) goto fail; floatbar->root_window = window; floatbar->flags = flags; floatbar->xfc = xfc; floatbar->locked = flags & 0x0002; xf_floatbar_toggle_fullscreen(floatbar, FALSE); return floatbar; fail: xf_floatbar_free(floatbar); return NULL; } static unsigned long xf_floatbar_get_color(xfFloatbar* floatbar, char* rgb_value) { Colormap cmap; XColor color; Display* display = floatbar->xfc->display; cmap = DefaultColormap(display, XDefaultScreen(display)); XParseColor(display, cmap, rgb_value, &color); XAllocColor(display, cmap, &color); return color.pixel; } static void xf_floatbar_event_expose(xfFloatbar* floatbar, XEvent* event) { GC gc, shape_gc; Pixmap pmap; XPoint shape[5], border[5]; int len; Display* display = floatbar->xfc->display; WINPR_UNUSED(event); /* create the pixmap that we'll use for shaping the window */ pmap = XCreatePixmap(display, floatbar->handle, floatbar->width, floatbar->height, 1); gc = XCreateGC(display, floatbar->handle, 0, 0); shape_gc = XCreateGC(display, pmap, 0, 0); /* points for drawing the floatbar */ shape[0].x = 0; shape[0].y = 0; shape[1].x = floatbar->width; shape[1].y = 0; shape[2].x = shape[1].x - FLOATBAR_BORDER; shape[2].y = FLOATBAR_HEIGHT; shape[3].x = shape[0].x + FLOATBAR_BORDER; shape[3].y = FLOATBAR_HEIGHT; shape[4].x = shape[0].x; shape[4].y = shape[0].y; /* points for drawing the border of the floatbar */ border[0].x = shape[0].x; border[0].y = shape[0].y - 1; border[1].x = shape[1].x - 1; border[1].y = shape[1].y - 1; border[2].x = shape[2].x; border[2].y = shape[2].y - 1; border[3].x = shape[3].x - 1; border[3].y = shape[3].y - 1; border[4].x = border[0].x; border[4].y = border[0].y; /* Fill all pixels with 0 */ XSetForeground(display, shape_gc, 0); XFillRectangle(display, pmap, shape_gc, 0, 0, floatbar->width, floatbar->height); /* Fill all pixels which should be shown with 1 */ XSetForeground(display, shape_gc, 1); XFillPolygon(display, pmap, shape_gc, shape, 5, 0, CoordModeOrigin); XShapeCombineMask(display, floatbar->handle, ShapeBounding, 0, 0, pmap, ShapeSet); /* draw the float bar */ XSetForeground(display, gc, xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_BACKGROUND)); XFillPolygon(display, floatbar->handle, gc, shape, 4, 0, CoordModeOrigin); /* draw an border for the floatbar */ XSetForeground(display, gc, xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_BORDER)); XDrawLines(display, floatbar->handle, gc, border, 5, CoordModeOrigin); /* draw the host name connected to (limit to maximum file name) */ len = strnlen(floatbar->title, MAX_PATH); XSetForeground(display, gc, xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_FOREGROUND)); XDrawString(display, floatbar->handle, gc, floatbar->width / 2 - len * 2, 15, floatbar->title, len); XFreeGC(display, gc); XFreeGC(display, shape_gc); } static xfFloatbarButton* xf_floatbar_get_button(xfFloatbar* floatbar, XEvent* event) { int i, size; size = ARRAYSIZE(floatbar->buttons); for (i = 0; i < size; i++) { if (floatbar->buttons[i]->handle == event->xany.window) { return floatbar->buttons[i]; } } return NULL; } static void xf_floatbar_button_update_positon(xfFloatbar* floatbar, XEvent* event) { xfFloatbarButton* button; int i, size; xfContext* xfc = floatbar->xfc; size = ARRAYSIZE(floatbar->buttons); for (i = 0; i < size; i++) { button = floatbar->buttons[i]; switch (button->type) { case XF_FLOATBAR_BUTTON_CLOSE: button->x = floatbar->width - FLOATBAR_BORDER - FLOATBAR_BUTTON_WIDTH * button->type; break; case XF_FLOATBAR_BUTTON_RESTORE: button->x = floatbar->width - FLOATBAR_BORDER - FLOATBAR_BUTTON_WIDTH * button->type; break; case XF_FLOATBAR_BUTTON_MINIMIZE: button->x = floatbar->width - FLOATBAR_BORDER - FLOATBAR_BUTTON_WIDTH * button->type; break; default: break; } XMoveWindow(xfc->display, button->handle, button->x, button->y); xf_floatbar_event_expose(floatbar, event); } } static void xf_floatbar_button_event_expose(xfFloatbar* floatbar, XEvent* event) { xfFloatbarButton* button = xf_floatbar_get_button(floatbar, event); static unsigned char* bits; GC gc; Pixmap pattern; xfContext* xfc = floatbar->xfc; if (!button) return; gc = XCreateGC(xfc->display, button->handle, 0, 0); floatbar = xfc->window->floatbar; switch (button->type) { case XF_FLOATBAR_BUTTON_CLOSE: bits = close_bits; break; case XF_FLOATBAR_BUTTON_RESTORE: bits = restore_bits; break; case XF_FLOATBAR_BUTTON_MINIMIZE: bits = minimize_bits; break; case XF_FLOATBAR_BUTTON_LOCKED: if (floatbar->locked) bits = lock_bits; else bits = unlock_bits; break; default: break; } pattern = XCreateBitmapFromData(xfc->display, button->handle, (const char*)bits, FLOATBAR_BUTTON_WIDTH, FLOATBAR_BUTTON_WIDTH); if (!(button->focus)) XSetForeground(xfc->display, gc, xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_BACKGROUND)); else XSetForeground(xfc->display, gc, xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_BORDER)); XSetBackground(xfc->display, gc, xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_FOREGROUND)); XCopyPlane(xfc->display, pattern, button->handle, gc, 0, 0, FLOATBAR_BUTTON_WIDTH, FLOATBAR_BUTTON_WIDTH, 0, 0, 1); XFreePixmap(xfc->display, pattern); XFreeGC(xfc->display, gc); } static void xf_floatbar_button_event_buttonpress(xfFloatbar* floatbar, XEvent* event) { xfFloatbarButton* button = xf_floatbar_get_button(floatbar, event); if (button) button->clicked = TRUE; } static void xf_floatbar_button_event_buttonrelease(xfFloatbar* floatbar, XEvent* event) { xfFloatbarButton* button; button = xf_floatbar_get_button(floatbar, event); if (button) { if (button->clicked) button->onclick(floatbar); } } static void xf_floatbar_event_buttonpress(xfFloatbar* floatbar, XEvent* event) { switch (event->xbutton.button) { case Button1: if (event->xmotion.x <= FLOATBAR_BORDER) floatbar->mode = XF_FLOATBAR_MODE_RESIZE_LEFT; else if (event->xmotion.x >= (floatbar->width - FLOATBAR_BORDER)) floatbar->mode = XF_FLOATBAR_MODE_RESIZE_RIGHT; else floatbar->mode = XF_FLOATBAR_MODE_DRAGGING; break; default: break; } } static void xf_floatbar_event_buttonrelease(xfFloatbar* floatbar, XEvent* event) { switch (event->xbutton.button) { case Button1: floatbar->mode = XF_FLOATBAR_MODE_NONE; break; default: break; } } static void xf_floatbar_resize(xfFloatbar* floatbar, XEvent* event) { int x, width, movement; xfContext* xfc = floatbar->xfc; /* calculate movement which happened on the root window */ movement = event->xmotion.x_root - floatbar->last_motion_x_root; /* set x and width depending if movement happens on the left or right */ if (floatbar->mode == XF_FLOATBAR_MODE_RESIZE_LEFT) { x = floatbar->x + movement; width = floatbar->width + movement * -1; } else { x = floatbar->x; width = floatbar->width + movement; } /* only resize and move window if still above minimum width */ if (FLOATBAR_MIN_WIDTH < width) { XMoveResizeWindow(xfc->display, floatbar->handle, x, 0, width, floatbar->height); floatbar->x = x; floatbar->width = width; } } static void xf_floatbar_dragging(xfFloatbar* floatbar, XEvent* event) { int x, movement; xfContext* xfc = floatbar->xfc; /* calculate movement and new x position */ movement = event->xmotion.x_root - floatbar->last_motion_x_root; x = floatbar->x + movement; /* do nothing if floatbar would be moved out of the window */ if (x < 0 || (x + floatbar->width) > xfc->window->width) return; /* move window to new x position */ XMoveWindow(xfc->display, floatbar->handle, x, 0); /* update struct values for the next event */ floatbar->last_motion_x_root = floatbar->last_motion_x_root + movement; floatbar->x = x; } static void xf_floatbar_event_motionnotify(xfFloatbar* floatbar, XEvent* event) { int mode; Cursor cursor; xfContext* xfc = floatbar->xfc; mode = floatbar->mode; cursor = XCreateFontCursor(xfc->display, XC_arrow); if ((event->xmotion.state & Button1Mask) && (mode > XF_FLOATBAR_MODE_DRAGGING)) { xf_floatbar_resize(floatbar, event); } else if ((event->xmotion.state & Button1Mask) && (mode == XF_FLOATBAR_MODE_DRAGGING)) { xf_floatbar_dragging(floatbar, event); } else { if (event->xmotion.x <= FLOATBAR_BORDER || event->xmotion.x >= floatbar->width - FLOATBAR_BORDER) cursor = XCreateFontCursor(xfc->display, XC_sb_h_double_arrow); } XDefineCursor(xfc->display, xfc->window->handle, cursor); XFreeCursor(xfc->display, cursor); floatbar->last_motion_x_root = event->xmotion.x_root; } static void xf_floatbar_button_event_focusin(xfFloatbar* floatbar, XEvent* event) { xfFloatbarButton* button; button = xf_floatbar_get_button(floatbar, event); if (button) { button->focus = TRUE; xf_floatbar_button_event_expose(floatbar, event); } } static void xf_floatbar_button_event_focusout(xfFloatbar* floatbar, XEvent* event) { xfFloatbarButton* button; button = xf_floatbar_get_button(floatbar, event); if (button) { button->focus = FALSE; button->clicked = FALSE; xf_floatbar_button_event_expose(floatbar, event); } } static void xf_floatbar_event_focusout(xfFloatbar* floatbar, XEvent* event) { xfContext* xfc = floatbar->xfc; WINPR_UNUSED(event); if (xfc->pointer) { XDefineCursor(xfc->display, xfc->window->handle, xfc->pointer->cursor); } } BOOL xf_floatbar_check_event(xfFloatbar* floatbar, XEvent* event) { xfFloatbarButton* button; size_t i, size; if (!floatbar || !floatbar->xfc || !event) return FALSE; if (!floatbar->created) return FALSE; if (event->xany.window == floatbar->handle) return TRUE; size = ARRAYSIZE(floatbar->buttons); for (i = 0; i < size; i++) { button = floatbar->buttons[i]; if (event->xany.window == button->handle) return TRUE; } return FALSE; } BOOL xf_floatbar_event_process(xfFloatbar* floatbar, XEvent* event) { if (!floatbar || !floatbar->xfc || !event) return FALSE; if (!floatbar->created) return FALSE; switch (event->type) { case Expose: if (event->xany.window == floatbar->handle) xf_floatbar_event_expose(floatbar, event); else xf_floatbar_button_event_expose(floatbar, event); break; case MotionNotify: xf_floatbar_event_motionnotify(floatbar, event); break; case ButtonPress: if (event->xany.window == floatbar->handle) xf_floatbar_event_buttonpress(floatbar, event); else xf_floatbar_button_event_buttonpress(floatbar, event); break; case ButtonRelease: if (event->xany.window == floatbar->handle) xf_floatbar_event_buttonrelease(floatbar, event); else xf_floatbar_button_event_buttonrelease(floatbar, event); break; case EnterNotify: case FocusIn: if (event->xany.window != floatbar->handle) xf_floatbar_button_event_focusin(floatbar, event); break; case LeaveNotify: case FocusOut: if (event->xany.window == floatbar->handle) xf_floatbar_event_focusout(floatbar, event); else xf_floatbar_button_event_focusout(floatbar, event); break; case ConfigureNotify: if (event->xany.window == floatbar->handle) xf_floatbar_button_update_positon(floatbar, event); break; case PropertyNotify: if (event->xany.window == floatbar->handle) xf_floatbar_button_update_positon(floatbar, event); break; default: break; } return floatbar->handle == event->xany.window; } static void xf_floatbar_button_free(xfContext* xfc, xfFloatbarButton* button) { if (!button) return; if (button->handle) { XUnmapWindow(xfc->display, button->handle); XDestroyWindow(xfc->display, button->handle); } free(button); } void xf_floatbar_free(xfFloatbar* floatbar) { size_t i, size; xfContext* xfc; if (!floatbar) return; free(floatbar->title); xfc = floatbar->xfc; size = ARRAYSIZE(floatbar->buttons); for (i = 0; i < size; i++) { xf_floatbar_button_free(xfc, floatbar->buttons[i]); floatbar->buttons[i] = NULL; } if (floatbar->handle) { XUnmapWindow(xfc->display, floatbar->handle); XDestroyWindow(xfc->display, floatbar->handle); } free(floatbar); }