/* Copyright (C) 2009 Red Hat, Inc. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see . */ #include "common.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "platform.h" #include "application.h" #include "utils.h" #include "x_platform.h" #include "debug.h" #include "monitor.h" #include "rect.h" #include "record.h" #include "playback.h" #include "resource.h" #include "res.h" #include "cursor.h" #include "process_loop.h" #define DWORD uint32_t #define BOOL bool #include "named_pipe.h" //#define X_DEBUG_SYNC(display) XSync(display, False) #define X_DEBUG_SYNC(display) #ifdef HAVE_XRANDR12 #define USE_XRANDR_1_2 #endif static Display* x_display = NULL; static bool x_shm_avail = false; static XVisualInfo **vinfo = NULL; static RedDrawable::Format *screen_format = NULL; static GLXFBConfig **fb_config = NULL; static XIM x_input_method = NULL; static XIC x_input_context = NULL; static XContext win_proc_context; static ProcessLoop* main_loop = NULL; static int focus_count = 0; static bool using_xrandr_1_0 = false; #ifdef USE_XRANDR_1_2 static bool using_xrandr_1_2 = false; #endif static int xrandr_event_base; static int xrandr_error_base; static int xrandr_major = 0; static int xrandr_minor = 0; static bool using_xrender_0_5 = false; static unsigned int caps_lock_mask = 0; static unsigned int num_lock_mask = 0; class DefaultEventListener: public Platform::EventListener { public: virtual void on_app_activated() {} virtual void on_app_deactivated() {} virtual void on_monitors_change() {} }; static DefaultEventListener default_event_listener; static Platform::EventListener* event_listener = &default_event_listener; class DefaultDisplayModeListner: public Platform::DisplayModeListner { public: void on_display_mode_change() {} }; static DefaultDisplayModeListner default_display_mode_listener; static Platform::DisplayModeListner* display_mode_listener = &default_display_mode_listener; NamedPipe::ListenerRef NamedPipe::create(const char *name, ListenerInterface& listener_interface) { ASSERT(main_loop && main_loop->is_same_thread(pthread_self())); return (ListenerRef)(new LinuxListener(name, listener_interface, *main_loop)); } void NamedPipe::destroy(ListenerRef listener_ref) { ASSERT(main_loop && main_loop->is_same_thread(pthread_self())); delete (LinuxListener *)listener_ref; } void NamedPipe::destroy_connection(ConnectionRef conn_ref) { ASSERT(main_loop && main_loop->is_same_thread(pthread_self())); delete (Session *)conn_ref; } int32_t NamedPipe::read(ConnectionRef conn_ref, uint8_t* buf, int32_t size) { if (((Session *)conn_ref) != NULL) { return ((Session *)conn_ref)->read(buf, size); } return -1; } int32_t NamedPipe::write(ConnectionRef conn_ref, const uint8_t* buf, int32_t size) { if (((Session *)conn_ref) != NULL) { return ((Session *)conn_ref)->write(buf, size); } return -1; } class XEventHandler: public EventSources::File { public: XEventHandler(Display& x_display, XContext& win_proc_context); virtual void on_event(); virtual int get_fd() {return _x_fd;} private: Display& _x_display; XContext& _win_proc_context; int _x_fd; }; XEventHandler::XEventHandler(Display& x_display, XContext& win_proc_context) : _x_display (x_display) , _win_proc_context (win_proc_context) { if ((_x_fd = ConnectionNumber(&x_display)) == -1) { THROW("get x fd failed"); } } void XEventHandler::on_event() { while (XPending(&_x_display)) { XPointer proc_pointer; XEvent event; XNextEvent(&_x_display, &event); if (event.xany.window == None) { LOG_WARN("invalid window"); continue; } if (XFilterEvent(&event, None)) { continue; } if (XFindContext(&_x_display, event.xany.window, _win_proc_context, &proc_pointer)) { THROW("no window proc"); } ((XPlatform::win_proc_t)proc_pointer)(event); } } Display* XPlatform::get_display() { return x_display; } bool XPlatform::is_x_shm_avail() { return x_shm_avail; } XImage *XPlatform::create_x_shm_image(RedDrawable::Format format, int width, int height, int depth, Visual *visual, XShmSegmentInfo **shminfo_out) { XImage *image; XShmSegmentInfo *shminfo; shminfo = new XShmSegmentInfo; shminfo->shmid = -1; shminfo->shmaddr = NULL; image = XShmCreateImage(XPlatform::get_display(), format == RedDrawable::A1 ? NULL : visual, format == RedDrawable::A1 ? 1 : depth, format == RedDrawable::A1 ? XYBitmap : ZPixmap, NULL, shminfo, width, height); if (image == NULL) { goto err1; } shminfo->shmid = shmget(IPC_PRIVATE, height * image->bytes_per_line, IPC_CREAT | 0777); if (shminfo->shmid < 0) { goto err2; } shminfo->shmaddr = (char *)shmat(shminfo->shmid, 0, 0); if (!shminfo->shmaddr) { goto err2; } shminfo->readOnly = False; if (!XShmAttach(XPlatform::get_display(), shminfo)) { goto err2; } image->data = (char *)shminfo->shmaddr; *shminfo_out = shminfo; return image; err2: XDestroyImage(image); if (shminfo->shmaddr != NULL) { shmdt(shminfo->shmaddr); } if (shminfo->shmid != -1) { shmctl(shminfo->shmid, IPC_RMID, 0); } err1: delete shminfo; return NULL; } XImage *XPlatform::create_x_image(RedDrawable::Format format, int width, int height, int depth, Visual *visual, XShmSegmentInfo **shminfo_out) { XImage *image = NULL; uint8_t *data; size_t stride; *shminfo_out = NULL; if (XPlatform::is_x_shm_avail()) { image = XPlatform::create_x_shm_image(format, width, height, depth, visual, shminfo_out); } if (image != NULL) { return image; } stride = SPICE_ALIGN(width * RedDrawable::format_to_bpp (format), 32) / 8; /* Must use malloc here, not new, because XDestroyImage will free() it */ data = (uint8_t *)malloc(height * stride); if (data == NULL) { THROW("Out of memory"); } if (format == RedDrawable::A1) { image = XCreateImage(XPlatform::get_display(), NULL, 1, XYBitmap, 0, (char *)data, width, height, 32, stride); } else { image = XCreateImage(XPlatform::get_display(), visual, depth, ZPixmap, 0, (char *)data, width, height, 32, stride); } return image; } void XPlatform::free_x_image(XImage *image, XShmSegmentInfo *shminfo) { if (shminfo) { XShmDetach(XPlatform::get_display(), shminfo); } if (image) { XDestroyImage(image); } if (shminfo) { XSync(XPlatform::get_display(), False); shmdt(shminfo->shmaddr); delete shminfo; } } XVisualInfo** XPlatform::get_vinfo() { return vinfo; } RedDrawable::Format XPlatform::get_screen_format(int screen) { return screen_format[screen]; } GLXFBConfig** XPlatform::get_fbconfig() { return fb_config; } XIC XPlatform::get_input_context() { return x_input_context; } void XPlatform::set_win_proc(Window win, win_proc_t proc) { if (XSaveContext(x_display, win, win_proc_context, (XPointer)proc)) { THROW("set win proc pailed"); } } void XPlatform::cleare_win_proc(Window win) { XDeleteContext(x_display, win, win_proc_context); } void Platform::send_quit_request() { ASSERT(main_loop); main_loop->quit(0); } uint64_t Platform::get_monolithic_time() { struct timespec time_space; clock_gettime(CLOCK_MONOTONIC, &time_space); return uint64_t(time_space.tv_sec) * 1000 * 1000 * 1000 + uint64_t(time_space.tv_nsec); } void Platform::get_temp_dir(std::string& path) { path = "/tmp/"; } uint64_t Platform::get_process_id() { static uint64_t pid = uint64_t(getpid()); return pid; } uint64_t Platform::get_thread_id() { return uint64_t(syscall(SYS_gettid)); } void Platform::error_beep() { if (!x_display) { return; } XBell(x_display, 0); } void Platform::msleep(unsigned int millisec) { usleep(millisec * 1000); } void Platform::yield() { pthread_yield(); } void Platform::term_printf(const char* format, ...) { va_list ap; va_start(ap, format); vprintf(format, ap); va_end(ap); } void Platform::set_thread_priority(void* thread, Platform::ThreadPriority in_priority) { ASSERT(thread == NULL); int priority; switch (in_priority) { case PRIORITY_TIME_CRITICAL: priority = -20; break; case PRIORITY_HIGH: priority = -2; break; case PRIORITY_ABOVE_NORMAL: priority = -1; break; case PRIORITY_NORMAL: priority = 0; break; case PRIORITY_BELOW_NORMAL: priority = 1; break; case PRIORITY_LOW: priority = 2; break; case PRIORITY_IDLE: priority = 19; break; default: THROW("invalid priority %d", in_priority); } pid_t tid = syscall(SYS_gettid); if (setpriority(PRIO_PROCESS, tid, priority) == -1) { DBG(0, "setpriority failed %s", strerror(errno)); } } void Platform::set_event_listener(EventListener* listener) { event_listener = listener ? listener : &default_event_listener; } void Platform::set_display_mode_listner(DisplayModeListner* listener) { display_mode_listener = listener ? listener : &default_display_mode_listener; } #ifdef USE_XRANDR_1_2 class FreeScreenResources { public: void operator () (XRRScreenResources* res) { XRRFreeScreenResources(res);} }; typedef _AutoRes AutoScreenRes; class FreeOutputInfo { public: void operator () (XRROutputInfo* output_info) { XRRFreeOutputInfo(output_info);} }; typedef _AutoRes AutoOutputInfo; class FreeCrtcInfo { public: void operator () (XRRCrtcInfo* crtc_info) { XRRFreeCrtcInfo(crtc_info);} }; typedef _AutoRes AutoCrtcInfo; static XRRModeInfo* find_mod(XRRScreenResources* res, RRMode mode) { for (int i = 0; i < res->nmode; i++) { if (res->modes[i].id == mode) { return &res->modes[i]; } } return NULL; } #endif //#define SHOW_SCREEN_INFO #ifdef SHOW_SCREEN_INFO static float mode_refresh(XRRModeInfo *mode_info) { if (!mode_info->hTotal || !mode_info->vTotal) { return 0; } return ((float)mode_info->dotClock / ((float)mode_info->hTotal * (float)mode_info->vTotal)); } static void show_scren_info() { int screen = DefaultScreen(x_display); Window root_window = RootWindow(x_display, screen); int minWidth; int minHeight; int maxWidth; int maxHeight; AutoScreenRes res(XRRGetScreenResources(x_display, root_window)); if (!res.valid()) { throw Exception(fmt("%s: get screen resources failed") % __FUNCTION__); } XRRGetScreenSizeRange(x_display, root_window, &minWidth, &minHeight, &maxWidth, &maxHeight); printf("screen: min %dx%d max %dx%d\n", minWidth, minHeight, maxWidth, maxHeight); int i, j; for (i = 0; i < res->noutput; i++) { AutoOutputInfo output_info(XRRGetOutputInfo(x_display, res.get(), res->outputs[i])); printf("output %s", output_info->name); if (output_info->crtc == None) { printf(" crtc None"); } else { printf(" crtc 0x%lx", output_info->crtc); } switch (output_info->connection) { case RR_Connected: printf(" Connected"); break; case RR_Disconnected: printf(" Disconnected"); break; case RR_UnknownConnection: printf(" UnknownConnection"); break; } printf(" ncrtc %u nclone %u nmode %u\n", output_info->ncrtc, output_info->nclone, output_info->nmode); for (j = 0; j < output_info->nmode; j++) { XRRModeInfo* mode = find_mod(res.get(), output_info->modes[j]); printf("\t%lu:", output_info->modes[j]); if (!mode) { printf(" ???\n"); continue; } printf(" %s %ux%u %f\n", mode->name, mode->width, mode->height, mode_refresh(mode)); } } for (i = 0; i < res->ncrtc; i++) { AutoCrtcInfo crtc_info(XRRGetCrtcInfo(x_display, res.get(), res->crtcs[i])); printf("crtc: 0x%lx x %d y %d width %u height %u mode %lu\n", res->crtcs[i], crtc_info->x, crtc_info->y, crtc_info->width, crtc_info->height, crtc_info->mode); } } #endif enum RedScreenRotation { RED_SCREEN_ROTATION_0, RED_SCREEN_ROTATION_90, RED_SCREEN_ROTATION_180, RED_SCREEN_ROTATION_270, }; enum RedSubpixelOrder { RED_SUBPIXEL_ORDER_UNKNOWN, RED_SUBPIXEL_ORDER_H_RGB, RED_SUBPIXEL_ORDER_H_BGR, RED_SUBPIXEL_ORDER_V_RGB, RED_SUBPIXEL_ORDER_V_BGR, RED_SUBPIXEL_ORDER_NONE, }; static void root_win_proc(XEvent& event); static void process_monitor_configure_events(Window root); class XMonitor; typedef std::list XMonitorsList; class XScreen { public: XScreen(Display* display, int screen); virtual ~XScreen() {} virtual void publish_monitors(MonitorsList& monitors) = 0; Display* get_display() {return _display;} int get_screen() {return _screen;} void set_broken() {_broken = true;} bool is_broken() const {return _broken;} int get_width() const {return _width;} void set_width(int width) {_width = width;} int get_height() const { return _height;} void set_height(int height) {_height = height;} SpicePoint get_position() const {return _position;} private: Display* _display; int _screen; SpicePoint _position; int _width; int _height; bool _broken; }; XScreen::XScreen(Display* display, int screen) : _display (display) , _screen (screen) , _broken (false) { int root = RootWindow(display, screen); XWindowAttributes attrib; XGetWindowAttributes(display, root, &attrib); _position.x = attrib.x; _position.y = attrib.y; _width = attrib.width; _height = attrib.height; } class StaticScreen: public XScreen, public Monitor { public: StaticScreen(Display* display, int screen, int& next_mon_id) : XScreen(display, screen) , Monitor(next_mon_id++) , _out_of_sync (false) { } virtual void publish_monitors(MonitorsList& monitors) { monitors.push_back(this); } virtual int get_depth() { return XPlatform::get_vinfo()[0]->depth;} virtual SpicePoint get_position() { return XScreen::get_position();} virtual SpicePoint get_size() const { SpicePoint pt = {get_width(), get_height()}; return pt;} virtual bool is_out_of_sync() { return _out_of_sync;} virtual int get_screen_id() { return get_screen();} protected: virtual void do_set_mode(int width, int height) { _out_of_sync = width > get_width() || height > get_height(); } virtual void do_restore() {} private: bool _out_of_sync; }; class DynamicScreen: public XScreen, public Monitor { public: DynamicScreen(Display* display, int screen, int& next_mon_id); virtual ~DynamicScreen(); void publish_monitors(MonitorsList& monitors); virtual int get_depth() { return XPlatform::get_vinfo()[0]->depth;} virtual SpicePoint get_position() { return XScreen::get_position();} virtual SpicePoint get_size() const { SpicePoint pt = {get_width(), get_height()}; return pt;} virtual bool is_out_of_sync() { return _out_of_sync;} virtual int get_screen_id() { return get_screen();} protected: virtual void do_set_mode(int width, int height); virtual void do_restore(); private: bool set_screen_size(int size_index); private: int _saved_width; int _saved_height; bool _out_of_sync; }; DynamicScreen::DynamicScreen(Display* display, int screen, int& next_mon_id) : XScreen(display, screen) , Monitor(next_mon_id++) , _saved_width (get_width()) , _saved_height (get_height()) , _out_of_sync (false) { X_DEBUG_SYNC(display); Window root_window = RootWindow(display, screen); XSelectInput(display, root_window, StructureNotifyMask); XRRSelectInput(display, root_window, RRScreenChangeNotifyMask); XPlatform::set_win_proc(root_window, root_win_proc); X_DEBUG_SYNC(display); } DynamicScreen::~DynamicScreen() { restore(); } void DynamicScreen::publish_monitors(MonitorsList& monitors) { monitors.push_back(this); } class SizeInfo { public: SizeInfo(int int_index, XRRScreenSize* in_size) : index (int_index), size (in_size) {} int index; XRRScreenSize* size; }; class SizeCompare { public: bool operator () (const SizeInfo& size1, const SizeInfo& size2) const { int area1 = size1.size->width * size1.size->height; int area2 = size2.size->width * size2.size->height; return area1 < area2 || (area1 == area2 && size1.index < size2.index); } }; void DynamicScreen::do_set_mode(int width, int height) { int num_sizes; X_DEBUG_SYNC(get_display()); XRRScreenSize* sizes = XRRSizes(get_display(), get_screen(), &num_sizes); typedef std::set SizesSet; SizesSet sizes_set; for (int i = 0; i < num_sizes; i++) { if (sizes[i].width >= width && sizes[i].height >= height) { sizes_set.insert(SizeInfo(i, &sizes[i])); } } _out_of_sync = true; if (!sizes_set.empty() && set_screen_size((*sizes_set.begin()).index)) { _out_of_sync = false; } X_DEBUG_SYNC(get_display()); } void DynamicScreen::do_restore() { X_DEBUG_SYNC(get_display()); if (is_broken() || (get_width() == _saved_width && get_height() == _saved_height)) { return; } int num_sizes; XRRScreenSize* sizes = XRRSizes(get_display(), get_screen(), &num_sizes); for (int i = 0; i < num_sizes; i++) { if (sizes[i].width == _saved_width && sizes[i].height == _saved_height) { set_screen_size(i); return; } } X_DEBUG_SYNC(get_display()); LOG_WARN("can't find startup mode"); } bool DynamicScreen::set_screen_size(int size_index) { X_DEBUG_SYNC(get_display()); Window root_window = RootWindow(get_display(), get_screen()); XRRScreenConfiguration* config; if (!(config = XRRGetScreenInfo(get_display(), root_window))) { LOG_WARN("get scren info failed"); return false; } Rotation rotation; XRRConfigCurrentConfiguration(config, &rotation); Monitor::self_monitors_change++; /*what status*/ XRRSetScreenConfig(get_display(), config, root_window, size_index, rotation, CurrentTime); process_monitor_configure_events(root_window); Monitor::self_monitors_change--; XRRFreeScreenConfigInfo(config); X_DEBUG_SYNC(get_display()); int num_sizes; XRRScreenSize* sizes = XRRSizes(get_display(), get_screen(), &num_sizes); if (num_sizes <= size_index) { THROW("invalid sizes size"); } set_width(sizes[size_index].width); set_height(sizes[size_index].height); return true; } #ifdef USE_XRANDR_1_2 class MultyMonScreen: public XScreen { public: MultyMonScreen(Display* display, int screen, int& next_mon_id); virtual ~MultyMonScreen(); virtual void publish_monitors(MonitorsList& monitors); void disable(); void enable(); bool set_monitor_mode(XMonitor& monitor, const XRRModeInfo& mode_info); private: void set_size(int width, int height); void get_trans_size(int& width, int& hight); SpicePoint get_trans_top_left(); SpicePoint get_trans_bottom_right(); bool changed(); XMonitor* crtc_overlap_test(int x, int y, int width, int height); void monitors_cleanup(); void restore(); private: int _min_width; int _min_height; int _max_width; int _max_height; int _saved_width; int _saved_height; int _saved_width_mm; int _saved_height_mm; XMonitorsList _monitors; }; #define MAX_TRANS_DEPTH 3 class XMonitor: public Monitor { public: XMonitor(MultyMonScreen& container, int id, RRCrtc crtc); virtual ~XMonitor(); virtual int get_depth(); virtual SpicePoint get_position(); virtual SpicePoint get_size() const; virtual bool is_out_of_sync(); virtual int get_screen_id() { return _container.get_screen();} void add_clone(XMonitor *clone); void revert(); void disable(); void enable(); void set_mode(const XRRModeInfo& mode); const SpiceRect& get_prev_area(); SpiceRect& get_trans_area(); void pin() { _pin_count++;} void unpin() { ASSERT(_pin_count > 0); _pin_count--;} bool is_pinned() {return !!_pin_count;} void commit_trans_position(); void set_pusher(XMonitor& pusher) { _pusher = &pusher;} XMonitor* get_pusher() { return _pusher;} void push_trans(); void begin_trans(); bool mode_changed(); bool position_changed(); static void inc_change_ref() { Monitor::self_monitors_change++;} static void dec_change_ref() { Monitor::self_monitors_change--;} protected: virtual void do_set_mode(int width, int height); virtual void do_restore(); private: void update_position(); bool finde_mode_in_outputs(RRMode mode, int start_index, XRRScreenResources* res); bool finde_mode_in_clones(RRMode mode, XRRScreenResources* res); XRRModeInfo* find_mode(int width, int height, XRRScreenResources* res); private: MultyMonScreen& _container; RRCrtc _crtc; XMonitorsList _clones; SpicePoint _position; SpicePoint _size; RRMode _mode; Rotation _rotation; int _noutput; RROutput* _outputs; SpicePoint _saved_position; SpicePoint _saved_size; RRMode _saved_mode; Rotation _saved_rotation; bool _out_of_sync; RedScreenRotation _red_rotation; RedSubpixelOrder _subpixel_order; int _trans_depth; SpiceRect _trans_area[MAX_TRANS_DEPTH]; int _pin_count; XMonitor* _pusher; }; MultyMonScreen::MultyMonScreen(Display* display, int screen, int& next_mon_id) : XScreen(display, screen) , _saved_width (get_width()) , _saved_height (get_height()) , _saved_width_mm (DisplayWidthMM(display, screen)) , _saved_height_mm (DisplayHeightMM(display, screen)) { X_DEBUG_SYNC(get_display()); Window root_window = RootWindow(display, screen); XRRGetScreenSizeRange(display, root_window, &_min_width, &_min_height, &_max_width, &_max_height); AutoScreenRes res(XRRGetScreenResources(display, root_window)); if (!res.valid()) { THROW("get screen resources failed"); } #ifdef SHOW_SCREEN_INFO show_scren_info(); #endif try { for (int i = 0; i < res->ncrtc; i++) { AutoCrtcInfo crtc_info(XRRGetCrtcInfo(display, res.get(), res->crtcs[i])); if (!crtc_info.valid()) { THROW("get crtc info failed"); } if (crtc_info->mode == None) { continue; } ASSERT(crtc_info->noutput); XMonitor* clone_mon = crtc_overlap_test(crtc_info->x, crtc_info->y, crtc_info->width, crtc_info->height); if (clone_mon) { clone_mon->add_clone(new XMonitor(*this, next_mon_id++, res->crtcs[i])); continue; } _monitors.push_back(new XMonitor(*this, next_mon_id++, res->crtcs[i])); } } catch (...) { monitors_cleanup(); throw; } XSelectInput(display, root_window, StructureNotifyMask); XRRSelectInput(display, root_window, RRScreenChangeNotifyMask); XPlatform::set_win_proc(root_window, root_win_proc); X_DEBUG_SYNC(get_display()); } MultyMonScreen::~MultyMonScreen() { restore(); monitors_cleanup(); } XMonitor* MultyMonScreen::crtc_overlap_test(int x, int y, int width, int height) { XMonitorsList::iterator iter = _monitors.begin(); for (; iter != _monitors.end(); iter++) { XMonitor* mon = *iter; SpicePoint pos = mon->get_position(); SpicePoint size = mon->get_size(); if (x == pos.x && y == pos.y && width == size.x && height == size.y) { return mon; } if (x < pos.x + size.x && x + width > pos.x && y < pos.y + size.y && y + height > pos.y) { THROW("unsupported partial overlap"); } } return NULL; } void MultyMonScreen::publish_monitors(MonitorsList& monitors) { XMonitorsList::iterator iter = _monitors.begin(); for (; iter != _monitors.end(); iter++) { monitors.push_back(*iter); } } void MultyMonScreen::monitors_cleanup() { while (!_monitors.empty()) { XMonitor* monitor = _monitors.front(); _monitors.pop_front(); delete monitor; } } void MultyMonScreen::disable() { XMonitorsList::iterator iter = _monitors.begin(); for (; iter != _monitors.end(); iter++) { (*iter)->disable(); } } void MultyMonScreen::enable() { XMonitorsList::iterator iter = _monitors.begin(); for (; iter != _monitors.end(); iter++) { (*iter)->enable(); } } void MultyMonScreen::set_size(int width, int height) { X_DEBUG_SYNC(get_display()); Window root_window = RootWindow(get_display(), get_screen()); set_width(width); int width_mm = (int)((double)_saved_width_mm / _saved_width * width); set_height(height); int height_mm = (int)((double)_saved_height_mm / _saved_height * height); XRRSetScreenSize(get_display(), root_window, width, height, width_mm, height_mm); X_DEBUG_SYNC(get_display()); } bool MultyMonScreen::changed() { if (get_width() != _saved_width || get_height() != _saved_height) { return true; } XMonitorsList::iterator iter = _monitors.begin(); for (; iter != _monitors.end(); iter++) { if ((*iter)->mode_changed() || (*iter)->position_changed()) { return true; } } return false; } void MultyMonScreen::restore() { if (is_broken() || !changed()) { return; } X_DEBUG_SYNC(get_display()); XMonitor::inc_change_ref(); disable(); Window root_window = RootWindow(get_display(), get_screen()); XRRSetScreenSize(get_display(), root_window, _saved_width, _saved_height, _saved_width_mm, _saved_height_mm); XMonitorsList::iterator iter = _monitors.begin(); for (; iter != _monitors.end(); iter++) { (*iter)->revert(); } enable(); process_monitor_configure_events(root_window); XMonitor::dec_change_ref(); X_DEBUG_SYNC(get_display()); } SpicePoint MultyMonScreen::get_trans_top_left() { SpicePoint position; position.y = position.x = MAXINT; XMonitorsList::iterator iter = _monitors.begin(); for (; iter != _monitors.end(); iter++) { SpiceRect& area = (*iter)->get_trans_area(); position.x = MIN(position.x, area.left); position.y = MIN(position.y, area.top); } return position; } SpicePoint MultyMonScreen::get_trans_bottom_right() { SpicePoint position; position.y = position.x = MININT; XMonitorsList::iterator iter = _monitors.begin(); for (; iter != _monitors.end(); iter++) { SpiceRect& area = (*iter)->get_trans_area(); position.x = MAX(position.x, area.right); position.y = MAX(position.y, area.bottom); } return position; } void MultyMonScreen::get_trans_size(int& width, int& height) { ASSERT(get_trans_top_left().x == 0 && get_trans_top_left().y == 0); SpicePoint bottom_right = get_trans_bottom_right(); ASSERT(bottom_right.x > 0 && bottom_right.y > 0); width = bottom_right.x; height = bottom_right.y; } #endif /*class Variant { static void get_area_in_front(const SpiceRect& base, int size, SpiceRect& area) static int get_push_distance(const SpiceRect& fix_area, const SpiceRect& other) static int get_head(const SpiceRect& area) static int get_tail(const SpiceRect& area) static void move_head(SpiceRect& area, int delta) static int get_pull_distance(const SpiceRect& fix_area, const SpiceRect& other) static void offset(SpiceRect& area, int delta) static void shrink(SpiceRect& area, int delta) static int get_distance(const SpiceRect& area, const SpiceRect& other_area) static bool is_on_tail(const SpiceRect& area, const SpiceRect& other_area) static bool is_on_perpendiculars(const SpiceRect& area, const SpiceRect& other_area) }*/ #ifdef USE_XRANDR_1_2 class SortRightToLeft { public: bool operator () (XMonitor* mon1, XMonitor* mon2) const { return mon1->get_trans_area().right > mon2->get_trans_area().right; } }; typedef std::multiset PushLeftSet; class LeftVariant { public: static void get_area_in_front(const SpiceRect& base, int size, SpiceRect& area) { area.right = base.left; area.left = area.right - size; area.bottom = base.bottom; area.top = base.top; } static int get_push_distance(const SpiceRect& fix_area, const SpiceRect& other) { return other.right - fix_area.left; } static int get_head(const SpiceRect& area) { return area.left; } static int get_tail(const SpiceRect& area) { return area.right; } static void move_head(SpiceRect& area, int delta) { area.left -= delta; ASSERT(area.right >= area.left); } static int get_pull_distance(const SpiceRect& fix_area, const SpiceRect& other) { return other.left - fix_area.right; } static void offset(SpiceRect& area, int delta) { rect_offset(area, -delta, 0); } static void shrink(SpiceRect& area, int delta) { area.right -= delta; ASSERT(area.right > area.left); } static int get_distance(const SpiceRect& area, const SpiceRect& other_area) { return other_area.left - area.left; } static bool is_on_tail(const SpiceRect& area, const SpiceRect& other_area) { return area.right == other_area.left && other_area.top < area.bottom && other_area.bottom > area.top; } static bool is_on_perpendiculars(const SpiceRect& area, const SpiceRect& other_area) { return (other_area.bottom == area.top || other_area.top == area.bottom) && other_area.left < area.right && other_area.right > area.left; } }; class SortLeftToRight { public: bool operator () (XMonitor* mon1, XMonitor* mon2) const { return mon1->get_trans_area().left < mon2->get_trans_area().left; } }; typedef std::multiset PushRightSet; class RightVariant { public: static void get_area_in_front(const SpiceRect& base, int size, SpiceRect& area) { area.left = base.right; area.right = area.left + size; area.top = base.top; area.bottom = base.bottom; } static int get_push_distance(const SpiceRect& fix_area, const SpiceRect& other) { return fix_area.right - other.left; } static int get_head(const SpiceRect& area) { return area.right; } static int get_tail(const SpiceRect& area) { return area.left; } static void move_head(SpiceRect& area, int delta) { area.right += delta; ASSERT(area.right >= area.left); } static int get_pull_distance(const SpiceRect& fix_area, const SpiceRect& other) { return fix_area.left - other.right; } static void offset(SpiceRect& area, int delta) { rect_offset(area, delta, 0); } static bool is_on_tail(const SpiceRect& area, const SpiceRect& other_area) { return other_area.right == area.left && other_area.top < area.bottom && other_area.bottom > area.top; } static bool is_on_perpendiculars(const SpiceRect& area, const SpiceRect& other_area) { return (other_area.bottom == area.top || other_area.top == area.bottom) && other_area.left < area.right && other_area.right > area.left; } }; class SortBottomToTop { public: bool operator () (XMonitor* mon1, XMonitor* mon2) const { return mon1->get_trans_area().bottom > mon2->get_trans_area().bottom; } }; typedef std::multiset PushTopSet; class TopVariant { public: static void get_area_in_front(const SpiceRect& base, int size, SpiceRect& area) { area.left = base.left; area.right = base.right; area.bottom = base.top; area.top = area.bottom - size; } static int get_push_distance(const SpiceRect& fix_area, const SpiceRect& other) { return other.bottom - fix_area.top; } static int get_head(const SpiceRect& area) { return area.top; } static int get_tail(const SpiceRect& area) { return area.bottom; } static void move_head(SpiceRect& area, int delta) { area.top -= delta; ASSERT(area.bottom >= area.top); } static int get_pull_distance(const SpiceRect& fix_area, const SpiceRect& other) { return other.top - fix_area.bottom; } static void offset(SpiceRect& area, int delta) { rect_offset(area, 0, -delta); } static void shrink(SpiceRect& area, int delta) { area.bottom -= delta; ASSERT(area.bottom > area.top); } static int get_distance(const SpiceRect& area, const SpiceRect& other_area) { return other_area.top - area.top; } static bool is_on_tail(const SpiceRect& area, const SpiceRect& other_area) { return area.bottom == other_area.top && other_area.left < area.right && other_area.right > area.left; } static bool is_on_perpendiculars(const SpiceRect& area, const SpiceRect& other_area) { return (other_area.right == area.left || other_area.left == area.right) && other_area.top < area.bottom && other_area.bottom > area.top; } }; class SortTopToBottom { public: bool operator () (XMonitor* mon1, XMonitor* mon2) const { return mon1->get_trans_area().top < mon2->get_trans_area().top; } }; typedef std::multiset PushBottomSet; class BottomVariant { public: static void get_area_in_front(const SpiceRect& base, int size, SpiceRect& area) { area.left = base.left; area.right = base.right; area.top = base.bottom; area.bottom = area.top + size; } static int get_push_distance(const SpiceRect& fix_area, const SpiceRect& other) { return fix_area.bottom - other.top; } static int get_head(const SpiceRect& area) { return area.bottom; } static int get_tail(const SpiceRect& area) { return area.top; } static void move_head(SpiceRect& area, int delta) { area.bottom += delta; ASSERT(area.bottom >= area.top); } static int get_pull_distance(const SpiceRect& fix_area, const SpiceRect& other) { return fix_area.top - other.bottom; } static void offset(SpiceRect& area, int delta) { rect_offset(area, 0, delta); } static bool is_on_tail(const SpiceRect& area, const SpiceRect& other_area) { return other_area.bottom == area.top && other_area.left < area.right && other_area.right > area.left; } static bool is_on_perpendiculars(const SpiceRect& area, const SpiceRect& other_area) { return (other_area.right == area.left || other_area.left == area.right) && other_area.top < area.bottom && other_area.bottom > area.top; } }; volatile int wait_for_me = false; template static void bounce_back(XMonitor& monitor, const XMonitorsList& monitors, int head, int distance) { ASSERT(distance > 0); while (wait_for_me); for (XMonitorsList::const_iterator iter = monitors.begin(); iter != monitors.end(); iter++) { SpiceRect& area = (*iter)->get_trans_area(); if (Variant::get_tail(area) == head && (*iter)->get_pusher() == &monitor) { Variant::offset(area, -distance); bounce_back(**iter, monitors, Variant::get_head(area) + distance, distance); //todo: pull_back monitors on perpendiculars } } } template static int push(XMonitor& pusher, XMonitor& monitor, const XMonitorsList& monitors, int delta) { monitor.pin(); monitor.set_pusher(pusher); SortList sort; XMonitorsList::const_iterator iter = monitors.begin(); for (; iter != monitors.end(); iter++) { if (*iter == &monitor) { continue; } sort.insert(*iter); } SpiceRect area_to_clear; Variant::get_area_in_front(monitor.get_trans_area(), delta, area_to_clear); SortListIter sort_iter = sort.begin(); for (; sort_iter != sort.end(); sort_iter++) { const SpiceRect& other_area = (*sort_iter)->get_trans_area(); if (rect_intersects(area_to_clear, other_area)) { int distance = Variant::get_push_distance(area_to_clear, other_area); ASSERT(distance > 0); if (!(*sort_iter)->is_pinned()) { distance = distance - push(monitor, **sort_iter, monitors, distance); } if (distance) { delta -= distance; bounce_back(monitor, monitors, Variant::get_head(area_to_clear), distance); Variant::move_head(area_to_clear, -distance); } } } Variant::offset(monitor.get_trans_area(), delta); const SpiceRect& area = monitor.get_prev_area(); for (iter = monitors.begin(); iter != monitors.end(); iter++) { if ((*iter)->is_pinned()) { continue; } const SpiceRect& other_area = (*iter)->get_prev_area(); if (Variant::is_on_perpendiculars(area, other_area)) { int current_distance = Variant::get_pull_distance(monitor.get_trans_area(), (*iter)->get_trans_area()); int base_distance = Variant::get_pull_distance(area, other_area); int distance = current_distance - base_distance; if (distance > 0) { push(monitor, **iter, monitors, distance); } } else if (Variant::is_on_tail(area, other_area)) { int distance = Variant::get_pull_distance(monitor.get_trans_area(), (*iter)->get_trans_area()); ASSERT(distance >= 0); push(monitor, **iter, monitors, distance); } } return delta; } template static void pin(XMonitor& monitor, const XMonitorsList& monitors) { const SpiceRect& area = monitor.get_trans_area(); for (XMonitorsList::const_iterator iter = monitors.begin(); iter != monitors.end(); iter++) { const SpiceRect& other_area = (*iter)->get_trans_area(); if ((*iter)->is_pinned()) { continue; } if (Variant::is_on_tail(other_area, area) || Variant::is_on_perpendiculars(area, other_area)) { (*iter)->pin(); pin(**iter, monitors); } } } template static void shrink(XMonitor& monitor, const XMonitorsList& monitors, int delta) { monitor.pin(); pin(monitor, monitors); ASSERT(delta > 0); SortList sort; XMonitorsList::const_iterator iter = monitors.begin(); for (; iter != monitors.end(); iter++) { if (*iter == &monitor) { continue; } sort.insert(*iter); } const SpiceRect area = monitor.get_trans_area(); Variant::shrink(monitor.get_trans_area(), delta); for (SortListIter sort_iter = sort.begin(); sort_iter != sort.end(); sort_iter++) { const SpiceRect& other_area = (*sort_iter)->get_trans_area(); if (Variant::is_on_perpendiculars(area, other_area)) { int distance = Variant::get_distance(area, other_area); if (distance > 0) { distance = MIN(distance, delta); push(monitor, **sort_iter, monitors, distance); } } else if (Variant::is_on_tail(area, other_area)) { push(monitor, **sort_iter, monitors, delta); } } } template static void expand(XMonitor& monitor, const XMonitorsList& monitors, int delta) { monitor.pin(); ASSERT(delta > 0); SortList sort; XMonitorsList::const_iterator iter = monitors.begin(); for (; iter != monitors.end(); iter++) { if (*iter == &monitor) { continue; } sort.insert(*iter); } SpiceRect area_to_clear; Variant::get_area_in_front(monitor.get_trans_area(), delta, area_to_clear); for (SortListIter sort_iter = sort.begin(); sort_iter != sort.end(); sort_iter++) { const SpiceRect& other_area = (*sort_iter)->get_trans_area(); if (rect_intersects(area_to_clear, other_area)) { int distance = Variant::get_push_distance(area_to_clear, other_area); ASSERT(distance > 0); ASSERT(!(*sort_iter)->is_pinned()); #ifdef RED_DEBUG int actual = #endif push(monitor, **sort_iter, monitors, distance); ASSERT(actual == distance); } } Variant::move_head(monitor.get_trans_area(), delta); } bool MultyMonScreen::set_monitor_mode(XMonitor& monitor, const XRRModeInfo& mode_info) { if (is_broken()) { return false; } SpicePoint size = monitor.get_size(); int dx = mode_info.width - size.x; int dy = mode_info.height - size.y; XMonitorsList::iterator iter = _monitors.begin(); for (; iter != _monitors.end(); iter++) { (*iter)->begin_trans(); } if (dx > 0) { expand(monitor, _monitors, dx); } else if (dx < 0) { shrink(monitor, _monitors, -dx); } for (iter = _monitors.begin(); iter != _monitors.end(); iter++) { (*iter)->push_trans(); } if (dy > 0) { expand(monitor, _monitors, dy); } else if (dy < 0) { shrink(monitor, _monitors, -dy); } int screen_width; int screen_height; get_trans_size(screen_width, screen_height); if (screen_width > _max_width || screen_height > _max_height) { return false; } screen_width = MAX(screen_width, _min_width); screen_height = MAX(screen_height, _min_height); XMonitor::inc_change_ref(); disable(); for (iter = _monitors.begin(); iter != _monitors.end(); iter++) { (*iter)->commit_trans_position(); } X_DEBUG_SYNC(get_display()); monitor.set_mode(mode_info); set_size(screen_width, screen_height); enable(); Window root_window = RootWindow(get_display(), get_screen()); process_monitor_configure_events(root_window); XMonitor::dec_change_ref(); X_DEBUG_SYNC(get_display()); return true; } XMonitor::XMonitor(MultyMonScreen& container, int id, RRCrtc crtc) : Monitor(id) , _container (container) , _crtc (crtc) , _out_of_sync (false) { update_position(); _saved_position = _position; _saved_size = _size; _saved_mode = _mode; _saved_rotation = _rotation; } XMonitor::~XMonitor() { while (!_clones.empty()) { XMonitor* clone = _clones.front(); _clones.pop_front(); delete clone; } delete[] _outputs; } void XMonitor::update_position() { Display* display = _container.get_display(); X_DEBUG_SYNC(display); Window root_window = RootWindow(display, _container.get_screen()); AutoScreenRes res(XRRGetScreenResources(display, root_window)); if (!res.valid()) { THROW("get screen resources failed"); } AutoCrtcInfo crtc_info(XRRGetCrtcInfo(display, res.get(), _crtc)); ASSERT(crtc_info->noutput); _position.x = crtc_info->x; _position.y = crtc_info->y; _size.x = crtc_info->width; _size.y = crtc_info->height; switch (crtc_info->rotation & 0xf) { case RR_Rotate_0: _red_rotation = RED_SCREEN_ROTATION_0; break; case RR_Rotate_90: _red_rotation = RED_SCREEN_ROTATION_90; break; case RR_Rotate_180: _red_rotation = RED_SCREEN_ROTATION_180; break; case RR_Rotate_270: _red_rotation = RED_SCREEN_ROTATION_270; break; default: THROW("invalid rotation"); } if (crtc_info->noutput > 1) { //todo: set valid subpixel order in case all outputs share the same type _subpixel_order = RED_SUBPIXEL_ORDER_UNKNOWN; } else { AutoOutputInfo output_info(XRRGetOutputInfo(display, res.get(), crtc_info->outputs[0])); switch (output_info->subpixel_order) { case SubPixelUnknown: _subpixel_order = RED_SUBPIXEL_ORDER_UNKNOWN; break; case SubPixelHorizontalRGB: _subpixel_order = RED_SUBPIXEL_ORDER_H_RGB; break; case SubPixelHorizontalBGR: _subpixel_order = RED_SUBPIXEL_ORDER_H_BGR; break; case SubPixelVerticalRGB: _subpixel_order = RED_SUBPIXEL_ORDER_V_RGB; break; case SubPixelVerticalBGR: _subpixel_order = RED_SUBPIXEL_ORDER_V_BGR; break; case SubPixelNone: _subpixel_order = RED_SUBPIXEL_ORDER_NONE; break; default: THROW("invalid subpixel order"); } } _mode = crtc_info->mode; _rotation = crtc_info->rotation; _noutput = crtc_info->noutput; _outputs = new RROutput[_noutput]; memcpy(_outputs, crtc_info->outputs, _noutput * sizeof(RROutput)); X_DEBUG_SYNC(display); } bool XMonitor::finde_mode_in_outputs(RRMode mode, int start_index, XRRScreenResources* res) { int i, j; X_DEBUG_SYNC(_container.get_display()); for (i = start_index; i < _noutput; i++) { AutoOutputInfo output_info(XRRGetOutputInfo(_container.get_display(), res, _outputs[i])); for (j = 0; j < output_info->nmode; j++) { if (output_info->modes[j] == mode) { break; } } if (j == output_info->nmode) { return false; } } X_DEBUG_SYNC(_container.get_display()); return true; } bool XMonitor::finde_mode_in_clones(RRMode mode, XRRScreenResources* res) { XMonitorsList::iterator iter = _clones.begin(); for (; iter != _clones.end(); iter++) { if (!(*iter)->finde_mode_in_outputs(mode, 0, res)) { return false; } } return true; } class ModeInfo { public: ModeInfo(int int_index, XRRModeInfo* in_info) : index (int_index), info (in_info) {} int index; XRRModeInfo* info; }; class ModeCompare { public: bool operator () (const ModeInfo& mode1, const ModeInfo& mode2) const { int area1 = mode1.info->width * mode1.info->height; int area2 = mode2.info->width * mode2.info->height; return area1 < area2 || (area1 == area2 && mode1.index < mode2.index); } }; XRRModeInfo* XMonitor::find_mode(int width, int height, XRRScreenResources* res) { typedef std::set ModesSet; ModesSet modes_set; X_DEBUG_SYNC(_container.get_display()); AutoOutputInfo output_info(XRRGetOutputInfo(_container.get_display(), res, _outputs[0])); for (int i = 0; i < output_info->nmode; i++) { XRRModeInfo* mode_inf = find_mod(res, output_info->modes[i]); if (mode_inf->width >= width && mode_inf->height >= height) { modes_set.insert(ModeInfo(i, mode_inf)); } } while (!modes_set.empty()) { ModesSet::iterator iter = modes_set.begin(); if (!finde_mode_in_outputs((*iter).info->id, 1, res)) { modes_set.erase(iter); continue; } if (!finde_mode_in_clones((*iter).info->id, res)) { modes_set.erase(iter); continue; } return (*iter).info; } X_DEBUG_SYNC(_container.get_display()); return NULL; } void XMonitor::do_set_mode(int width, int height) { if (width == _size.x && height == _size.y) { _out_of_sync = false; return; } Display* display = _container.get_display(); X_DEBUG_SYNC(display); Window root_window = RootWindow(display, _container.get_screen()); AutoScreenRes res(XRRGetScreenResources(display, root_window)); if (!res.valid()) { THROW("get screen resource failed"); } XRRModeInfo* mode_info = find_mode(width, height, res.get()); if (!mode_info || !_container.set_monitor_mode(*this, *mode_info)) { _out_of_sync = true; X_DEBUG_SYNC(display); return; } _out_of_sync = false; } void XMonitor::revert() { _position = _saved_position; _size = _saved_size; _mode = _saved_mode; _rotation = _saved_rotation; XMonitorsList::iterator iter = _clones.begin(); for (; iter != _clones.end(); iter++) { (*iter)->revert(); } } void XMonitor::disable() { Display* display = _container.get_display(); X_DEBUG_SYNC(display); Window root_window = RootWindow(display, _container.get_screen()); AutoScreenRes res(XRRGetScreenResources(display, root_window)); if (!res.valid()) { THROW("get screen resources failed"); } XRRSetCrtcConfig(display, res.get(), _crtc, CurrentTime, 0, 0, None, RR_Rotate_0, NULL, 0); XMonitorsList::iterator iter = _clones.begin(); for (; iter != _clones.end(); iter++) { (*iter)->disable(); } X_DEBUG_SYNC(display); } void XMonitor::enable() { Display* display = _container.get_display(); X_DEBUG_SYNC(display); Window root_window = RootWindow(display, _container.get_screen()); AutoScreenRes res(XRRGetScreenResources(display, root_window)); if (!res.valid()) { THROW("get screen resources failed"); } XRRSetCrtcConfig(display, res.get(), _crtc, CurrentTime, _position.x, _position.y, _mode, _rotation, _outputs, _noutput); XMonitorsList::iterator iter = _clones.begin(); for (; iter != _clones.end(); iter++) { (*iter)->enable(); } X_DEBUG_SYNC(display); } bool XMonitor::mode_changed() { return _size.x != _saved_size.x || _size.y != _saved_size.y || _mode != _saved_mode || _rotation != _saved_rotation; } bool XMonitor::position_changed() { return _position.x != _saved_position.x || _position.y != _saved_position.y; } void XMonitor::do_restore() { if (!mode_changed()) { return; } do_set_mode(_saved_size.x, _saved_size.y); } int XMonitor::get_depth() { return XPlatform::get_vinfo()[0]->depth; } SpicePoint XMonitor::get_position() { return _position; } SpicePoint XMonitor::get_size() const { return _size; } bool XMonitor::is_out_of_sync() { return _out_of_sync; } void XMonitor::add_clone(XMonitor *clone) { _clones.push_back(clone); } const SpiceRect& XMonitor::get_prev_area() { return _trans_area[_trans_depth - 1]; } SpiceRect& XMonitor::get_trans_area() { return _trans_area[_trans_depth]; } void XMonitor::push_trans() { _trans_depth++; ASSERT(_trans_depth < MAX_TRANS_DEPTH); _trans_area[_trans_depth] = _trans_area[_trans_depth - 1]; _pin_count = 0; _pusher = NULL; } void XMonitor::begin_trans() { _trans_area[0].left = _position.x; _trans_area[0].right = _trans_area[0].left + _size.x; _trans_area[0].top = _position.y; _trans_area[0].bottom = _trans_area[0].top + _size.y; _trans_area[1] = _trans_area[0]; _trans_depth = 1; _pin_count = 0; _pusher = NULL; } void XMonitor::commit_trans_position() { _position.x = _trans_area[_trans_depth].left; _position.y = _trans_area[_trans_depth].top; XMonitorsList::iterator iter = _clones.begin(); for (; iter != _clones.end(); iter++) { (*iter)->_position = _position; } } void XMonitor::set_mode(const XRRModeInfo& mode) { _mode = mode.id; _size.x = mode.width; _size.y = mode.height; XMonitorsList::iterator iter = _clones.begin(); for (; iter != _clones.end(); iter++) { (*iter)->set_mode(mode); } } #endif static MonitorsList monitors; static Monitor* primary_monitor = NULL; typedef std::list ScreenList; static ScreenList screens; const MonitorsList& Platform::init_monitors() { int next_mon_id = 0; ASSERT(screens.empty()); #ifdef USE_XRANDR_1_2 if (using_xrandr_1_2) { for (int i = 0; i < ScreenCount(x_display); i++) { screens.push_back(new MultyMonScreen(x_display, i, next_mon_id)); } } else #endif if (using_xrandr_1_0) { for (int i = 0; i < ScreenCount(x_display); i++) { screens.push_back(new DynamicScreen(x_display, i, next_mon_id)); } } else { for (int i = 0; i < ScreenCount(x_display); i++) { screens.push_back(new StaticScreen(x_display, i, next_mon_id)); } } ASSERT(monitors.empty()); ScreenList::iterator iter = screens.begin(); for (; iter != screens.end(); iter++) { (*iter)->publish_monitors(monitors); } MonitorsList::iterator mon_iter = monitors.begin(); for (; mon_iter != monitors.end(); mon_iter++) { Monitor *mon = *mon_iter; if (mon->get_id() == 0) { primary_monitor = mon; break; } } return monitors; } void Platform::destroy_monitors() { primary_monitor = NULL; monitors.clear(); while (!screens.empty()) { XScreen* screen = screens.front(); screens.pop_front(); delete screen; } } bool Platform::is_monitors_pos_valid() { return (ScreenCount(x_display) == 1); } void Platform::get_app_data_dir(std::string& path, const std::string& app_name) { const char* home_dir = getenv("HOME"); if (!home_dir || strlen(home_dir) == 0) { throw Exception("get home dir failed"); } path = home_dir; std::string::iterator end = path.end(); while (end != path.begin() && *(end - 1) == '/') { path.erase(--end); } path += "/."; path += app_name; if (mkdir(path.c_str(), 0700) == -1 && errno != EEXIST) { throw Exception("create appdata dir failed"); } } void Platform::path_append(std::string& path, const std::string& partial_path) { path += "/"; path += partial_path; } static void root_win_proc(XEvent& event) { #ifdef USE_XRANDR_1_2 ASSERT(using_xrandr_1_0 || using_xrandr_1_2); #else ASSERT(using_xrandr_1_0); #endif if (event.type == ConfigureNotify || event.type - xrandr_event_base == RRScreenChangeNotify) { XRRUpdateConfiguration(&event); if (event.type - xrandr_event_base == RRScreenChangeNotify) { display_mode_listener->on_display_mode_change(); } if (Monitor::is_self_change()) { return; } ScreenList::iterator iter = screens.begin(); for (; iter != screens.end(); iter++) { (*iter)->set_broken(); } event_listener->on_monitors_change(); } /*switch (event.type - xrandr_event_base) { case RRScreenChangeNotify: //XRRScreenChangeNotifyEvent * = (XRRScreenChangeNotifyEvent *) &event; XRRUpdateConfiguration(&event); break; }*/ } static void process_monitor_configure_events(Window root) { XSync(x_display, False); XEvent event; while (XCheckTypedWindowEvent(x_display, root, ConfigureNotify, &event)) { root_win_proc(event); } while (XCheckTypedWindowEvent(x_display, root, xrandr_event_base + RRScreenChangeNotify, &event)) { root_win_proc(event); } } static void cleanup(void) { int i; DBG(0, ""); if (!Monitor::is_self_change()) { Platform::destroy_monitors(); } if (vinfo) { for (i = 0; i < ScreenCount(x_display); ++i) { XFree(vinfo[i]); } delete vinfo; vinfo = NULL; } if (fb_config) { for (i = 0; i < ScreenCount(x_display); ++i) { if (fb_config[i]) { XFree(fb_config[i]); } } delete fb_config; fb_config = NULL; } } static void quit_handler(int sig) { LOG_INFO("signal %d", sig); Platform::send_quit_request(); } static void abort_handler(int sig) { LOG_INFO("signal %d", sig); Platform::destroy_monitors(); } static void init_xrandr() { Bool xrandr_ext = XRRQueryExtension(x_display, &xrandr_event_base, &xrandr_error_base); if (xrandr_ext) { XRRQueryVersion(x_display, &xrandr_major, &xrandr_minor); if (xrandr_major < 1) { return; } #ifdef USE_XRANDR_1_2 if (xrandr_major == 1 && xrandr_minor < 2) { using_xrandr_1_0 = true; return; } using_xrandr_1_2 = true; #else using_xrandr_1_0 = true; #endif } } static void init_xrender() { int event_base; int error_base; int major; int minor; using_xrender_0_5 = XRenderQueryExtension(x_display, &event_base, &error_base) && XRenderQueryVersion(x_display, &major, &minor) && (major > 0 || minor >= 5); } unsigned int get_modifier_mask(KeySym modifier) { int mask = 0; int i; XModifierKeymap* map = XGetModifierMapping(x_display); KeyCode keycode = XKeysymToKeycode(x_display, modifier); if (keycode == NoSymbol) { return 0; } for (i = 0; i < 8; i++) { if (map->modifiermap[map->max_keypermod * i] == keycode) { mask = 1 << i; } } XFreeModifiermap(map); return mask; } static void init_kbd() { int xkb_major = XkbMajorVersion; int xkb_minor = XkbMinorVersion; int opcode; int event; int error; if (!XkbLibraryVersion(&xkb_major, &xkb_minor) || !XkbQueryExtension(x_display, &opcode, &event, &error, &xkb_major, &xkb_minor)) { return; } caps_lock_mask = get_modifier_mask(XK_Caps_Lock); num_lock_mask = get_modifier_mask(XK_Num_Lock); } static void init_XIM() { char app_name[20]; strcpy(app_name, "spicec"); XSetLocaleModifiers(""); x_input_method = XOpenIM(x_display, NULL, app_name, app_name); if (!x_input_method) { return; } x_input_context = XCreateIC(x_input_method, XNInputStyle, XIMPreeditNone | XIMStatusNone, NULL); if (!x_input_context) { THROW("create IC failed"); } } static int x_error_handler(Display* display, XErrorEvent* error_event) { char error_str[256]; char request_str[256]; char number_str[32]; char* display_name = XDisplayString(display); XGetErrorText(display, error_event->error_code, error_str, sizeof(error_str)); if (error_event->request_code < 128) { snprintf(number_str, sizeof(number_str), "%d", error_event->request_code); XGetErrorDatabaseText(display, "XRequest", number_str, "", request_str, sizeof(request_str)); } else { snprintf(request_str, sizeof(request_str), "%d", error_event->request_code); } LOG_ERROR("x error on display %s error %s minor %u request %s", display_name, error_str, (uint32_t)error_event->minor_code, request_str); exit(-1); return 0; } static int x_io_error_handler(Display* display) { LOG_ERROR("x io error on %s", XDisplayString(display)); exit(-1); return 0; } static XVisualInfo* get_x_vis_info(int screen) { XVisualInfo vtemplate; int count; Visual* visual = DefaultVisualOfScreen(ScreenOfDisplay(x_display, screen)); vtemplate.screen = screen; vtemplate.visualid = XVisualIDFromVisual(visual); return XGetVisualInfo(x_display, VisualIDMask | VisualScreenMask, &vtemplate, &count); } void Platform::init() { int err, ev; int threads_enable; int major, minor; Bool pixmaps; DBG(0, ""); setlocale(LC_ALL, ""); threads_enable = XInitThreads(); if (!(x_display = XOpenDisplay(NULL))) { THROW("open X display failed"); } if (XShmQueryExtension (x_display) && XShmQueryVersion (x_display, &major, &minor, &pixmaps)) { x_shm_avail = true; } vinfo = new XVisualInfo *[ScreenCount(x_display)]; memset(vinfo, 0, sizeof(XVisualInfo *) * ScreenCount(x_display)); fb_config = new GLXFBConfig *[ScreenCount(x_display)]; memset(fb_config, 0, sizeof(GLXFBConfig *) * ScreenCount(x_display)); screen_format = new RedDrawable::Format[ScreenCount(x_display)]; memset(screen_format, 0, sizeof(RedDrawable::Format) * ScreenCount(x_display)); if (threads_enable && glXQueryExtension(x_display, &err, &ev)) { int num_configs; int attrlist[] = { GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_DRAWABLE_TYPE, GLX_PBUFFER_BIT | GLX_WINDOW_BIT, GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, GLX_ALPHA_SIZE, 8, GLX_STENCIL_SIZE, 4, GLX_DEPTH_SIZE, 0, None }; for (int i = 0; i < ScreenCount(x_display); ++i) { fb_config[i] = glXChooseFBConfig(x_display, i, attrlist, &num_configs); if (fb_config[i] != NULL) { ASSERT(num_configs > 0); vinfo[i] = glXGetVisualFromFBConfig(x_display, fb_config[i][0]); } if (vinfo[i] == NULL) { if (fb_config[i]) { XFree(fb_config[i]); fb_config[i] = NULL; } vinfo[i] = get_x_vis_info(i); } } } else { for (int i = 0; i < ScreenCount(x_display); ++i) { vinfo[i] = get_x_vis_info(i); } } for (int i = 0; i < ScreenCount(x_display); ++i) { if (vinfo[i] == NULL) { THROW("Unable to find a visual for screen"); } if ((vinfo[i]->depth == 32 || vinfo[i]->depth == 24) && vinfo[i]->red_mask == 0xff0000 && vinfo[i]->green_mask == 0x00ff00 && vinfo[i]->blue_mask == 0x0000ff) { screen_format[i] = RedDrawable::RGB32; } else if (vinfo[i]->depth == 16 && vinfo[i]->red_mask == 0xf800 && vinfo[i]->green_mask == 0x7e0 && vinfo[i]->blue_mask == 0x1f) { screen_format[i] = RedDrawable::RGB16_565; } else if (vinfo[i]->depth == 15 && vinfo[i]->red_mask == 0x7c00 && vinfo[i]->green_mask == 0x3e0 && vinfo[i]->blue_mask == 0x1f) { screen_format[i] = RedDrawable::RGB16_555; } else { THROW("Unsupported visual for screen"); } } XSetErrorHandler(x_error_handler); XSetIOErrorHandler(x_io_error_handler); win_proc_context = XUniqueContext(); init_kbd(); init_xrandr(); init_xrender(); init_XIM(); struct sigaction act; memset(&act, 0, sizeof(act)); sigfillset(&act.sa_mask); act.sa_handler = quit_handler; sigaction(SIGINT, &act, NULL); sigaction(SIGHUP, &act, NULL); sigaction(SIGTERM, &act, NULL); sigaction(SIGQUIT, &act, NULL); act.sa_handler = SIG_IGN; sigaction(SIGPIPE, &act, NULL); act.sa_flags = SA_RESETHAND; act.sa_handler = abort_handler; sigaction(SIGABRT, &act, NULL); sigaction(SIGILL, &act, NULL); sigaction(SIGSEGV, &act, NULL); sigaction(SIGFPE, &act, NULL); atexit(cleanup); } void Platform::set_process_loop(ProcessLoop& main_process_loop) { main_loop = &main_process_loop; XEventHandler *x_event_handler; x_event_handler = new XEventHandler(*x_display, win_proc_context); main_loop->add_file(*x_event_handler); } uint32_t Platform::get_keyboard_lock_modifiers() { XKeyboardState keyboard_state; uint32_t modifiers = 0; XGetKeyboardControl(x_display, &keyboard_state); if (keyboard_state.led_mask & 0x01) { modifiers |= CAPS_LOCK_MODIFIER; } if (keyboard_state.led_mask & 0x02) { modifiers |= NUM_LOCK_MODIFIER; } if (keyboard_state.led_mask & 0x04) { modifiers |= SCROLL_LOCK_MODIFIER; } return modifiers; } enum XLed { X11_CAPS_LOCK_LED = 1, X11_NUM_LOCK_LED, X11_SCROLL_LOCK_LED, }; static void set_keyboard_led(XLed led, int set) { switch (led) { case X11_CAPS_LOCK_LED: if (caps_lock_mask) { XkbLockModifiers(x_display, XkbUseCoreKbd, caps_lock_mask, set ? caps_lock_mask : 0); } return; case X11_NUM_LOCK_LED: if (num_lock_mask) { XkbLockModifiers(x_display, XkbUseCoreKbd, num_lock_mask, set ? num_lock_mask : 0); } return; case X11_SCROLL_LOCK_LED: XKeyboardControl keyboard_control; keyboard_control.led_mode = set ? LedModeOn : LedModeOff; keyboard_control.led = led; XChangeKeyboardControl(x_display, KBLed | KBLedMode, &keyboard_control); return; } } void Platform::set_keyboard_lock_modifiers(uint32_t modifiers) { uint32_t now = get_keyboard_lock_modifiers(); if ((now & CAPS_LOCK_MODIFIER) != (modifiers & CAPS_LOCK_MODIFIER)) { set_keyboard_led(X11_CAPS_LOCK_LED, !!(modifiers & CAPS_LOCK_MODIFIER)); } if ((now & NUM_LOCK_MODIFIER) != (modifiers & NUM_LOCK_MODIFIER)) { set_keyboard_led(X11_NUM_LOCK_LED, !!(modifiers & NUM_LOCK_MODIFIER)); } if ((now & SCROLL_LOCK_MODIFIER) != (modifiers & SCROLL_LOCK_MODIFIER)) { set_keyboard_led(X11_SCROLL_LOCK_LED, !!(modifiers & SCROLL_LOCK_MODIFIER)); } } uint32_t key_bit(char* keymap, int key, uint32_t bit) { KeyCode key_code = XKeysymToKeycode(x_display, key); return (((keymap[key_code >> 3] >> (key_code & 7)) & 1) ? bit : 0); } uint32_t Platform::get_keyboard_modifiers() { char keymap[32]; XQueryKeymap(x_display, keymap); return key_bit(keymap, XK_Shift_L, L_SHIFT_MODIFIER) | key_bit(keymap, XK_Shift_R, R_SHIFT_MODIFIER) | key_bit(keymap, XK_Control_L, L_CTRL_MODIFIER) | key_bit(keymap, XK_Control_R, R_CTRL_MODIFIER) | key_bit(keymap, XK_Alt_L, L_ALT_MODIFIER) | key_bit(keymap, XK_Alt_R, R_ALT_MODIFIER); } void Platform::reset_cursor_pos() { if (!primary_monitor) { return; } SpicePoint pos = primary_monitor->get_position(); SpicePoint size = primary_monitor->get_size(); Window root_window = RootWindow(x_display, DefaultScreen(x_display)); XWarpPointer(x_display, None, root_window, 0, 0, 0, 0, pos.x + size.x / 2, pos.y + size.y / 2); } WaveRecordAbstract* Platform::create_recorder(RecordClient& client, uint32_t sampels_per_sec, uint32_t bits_per_sample, uint32_t channels) { return new WaveRecorder(client, sampels_per_sec, bits_per_sample, channels); } WavePlaybackAbstract* Platform::create_player(uint32_t sampels_per_sec, uint32_t bits_per_sample, uint32_t channels) { return new WavePlayer(sampels_per_sec, bits_per_sample, channels); } void XPlatform::on_focus_in() { if (focus_count++ == 0) { event_listener->on_app_activated(); } } void XPlatform::on_focus_out() { ASSERT(focus_count > 0); if (--focus_count == 0) { event_listener->on_app_deactivated(); } } class XBaseLocalCursor: public LocalCursor { public: XBaseLocalCursor() : _handle (0) {} ~XBaseLocalCursor(); void set(Window window); protected: Cursor _handle; }; void XBaseLocalCursor::set(Window window) { if (_handle) { XDefineCursor(x_display, window, _handle); } } XBaseLocalCursor::~XBaseLocalCursor() { if (_handle) { XFreeCursor(x_display, _handle); } } class XLocalCursor: public XBaseLocalCursor { public: XLocalCursor(CursorData* cursor_data); }; static inline uint8_t get_pix_mask(const uint8_t* data, int offset, int pix_index) { return data[offset + (pix_index >> 3)] & (0x80 >> (pix_index % 8)); } static inline uint32_t get_pix_hack(int pix_index, int width) { return (((pix_index % width) ^ (pix_index / width)) & 1) ? 0xc0303030 : 0x30505050; } XLocalCursor::XLocalCursor(CursorData* cursor_data) { const SpiceCursorHeader& header = cursor_data->header(); const uint8_t* data = cursor_data->data(); int cur_size = header.width * header.height; uint8_t pix_mask; uint32_t pix; uint16_t i; int size; if (!get_size_bits(header, size)) { THROW("invalid curosr type"); } uint32_t* cur_data = new uint32_t[cur_size]; switch (header.type) { case SPICE_CURSOR_TYPE_ALPHA: break; case SPICE_CURSOR_TYPE_COLOR32: memcpy(cur_data, data, cur_size * sizeof(uint32_t)); for (i = 0; i < cur_size; i++) { pix_mask = get_pix_mask(data, size, i); if (pix_mask && *((uint32_t*)data + i) == 0xffffff) { cur_data[i] = get_pix_hack(i, header.width); } else { cur_data[i] |= (pix_mask ? 0 : 0xff000000); } } break; case SPICE_CURSOR_TYPE_COLOR16: for (i = 0; i < cur_size; i++) { pix_mask = get_pix_mask(data, size, i); pix = *((uint16_t*)data + i); if (pix_mask && pix == 0x7fff) { cur_data[i] = get_pix_hack(i, header.width); } else { cur_data[i] = ((pix & 0x1f) << 3) | ((pix & 0x3e0) << 6) | ((pix & 0x7c00) << 9) | (pix_mask ? 0 : 0xff000000); } } break; case SPICE_CURSOR_TYPE_MONO: for (i = 0; i < cur_size; i++) { pix_mask = get_pix_mask(data, 0, i); pix = get_pix_mask(data, size, i); if (pix_mask && pix) { cur_data[i] = get_pix_hack(i, header.width); } else { cur_data[i] = (pix ? 0xffffff : 0) | (pix_mask ? 0 : 0xff000000); } } break; case SPICE_CURSOR_TYPE_COLOR4: for (i = 0; i < cur_size; i++) { pix_mask = get_pix_mask(data, size + (sizeof(uint32_t) << 4), i); int idx = (i & 1) ? (data[i >> 1] & 0x0f) : ((data[i >> 1] & 0xf0) >> 4); pix = *((uint32_t*)(data + size) + idx); if (pix_mask && pix == 0xffffff) { cur_data[i] = get_pix_hack(i, header.width); } else { cur_data[i] = pix | (pix_mask ? 0 : 0xff000000); } } break; case SPICE_CURSOR_TYPE_COLOR24: case SPICE_CURSOR_TYPE_COLOR8: default: LOG_WARN("unsupported cursor type %d", header.type); _handle = XCreateFontCursor(x_display, XC_arrow); delete[] cur_data; return; } XImage image; memset(&image, 0, sizeof(image)); image.width = header.width; image.height = header.height; image.data = (header.type == SPICE_CURSOR_TYPE_ALPHA ? (char*)data : (char*)cur_data); image.byte_order = LSBFirst; image.bitmap_unit = 32; image.bitmap_bit_order = LSBFirst; image.bitmap_pad = 8; image.bytes_per_line = header.width << 2; image.depth = 32; image.format = ZPixmap; image.bits_per_pixel = 32; image.red_mask = 0x00ff0000; image.green_mask = 0x0000ff00; image.blue_mask = 0x000000ff; if (!XInitImage(&image)) { THROW("init image failed"); } Window root_window = RootWindow(x_display, DefaultScreen(x_display)); Pixmap pixmap = XCreatePixmap(x_display, root_window, header.width, header.height, 32); XGCValues gc_vals; gc_vals.function = GXcopy; gc_vals.foreground = ~0; gc_vals.background = 0; gc_vals.plane_mask = AllPlanes; GC gc = XCreateGC(x_display, pixmap, GCFunction | GCForeground | GCBackground | GCPlaneMask, &gc_vals); XPutImage(x_display, pixmap, gc, &image, 0, 0, 0, 0, header.width, header.height); XFreeGC(x_display, gc); XRenderPictFormat *xformat = XRenderFindStandardFormat(x_display, PictStandardARGB32); Picture picture = XRenderCreatePicture(x_display, pixmap, xformat, 0, NULL); _handle = XRenderCreateCursor(x_display, picture, header.hot_spot_x, header.hot_spot_y); XRenderFreePicture(x_display, picture); XFreePixmap(x_display, pixmap); delete[] cur_data; } LocalCursor* Platform::create_local_cursor(CursorData* cursor_data) { ASSERT(using_xrender_0_5); return new XLocalCursor(cursor_data); } class XInactiveCursor: public XBaseLocalCursor { public: XInactiveCursor() { _handle = XCreateFontCursor(x_display, XC_X_cursor);} }; LocalCursor* Platform::create_inactive_cursor() { return new XInactiveCursor(); } class XDefaultCursor: public XBaseLocalCursor { public: XDefaultCursor() { _handle = XCreateFontCursor(x_display, XC_top_left_arrow);} }; LocalCursor* Platform::create_default_cursor() { return new XDefaultCursor(); }