/*
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
#include
#ifdef HAVE_SYS_TIME_H
#include
#endif
#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"
#include
#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;
#ifdef USE_OGL
static GLXFBConfig **fb_config = NULL;
#endif // USE_OGL
static XIM x_input_method = NULL;
static XIC x_input_context = NULL;
static Window platform_win;
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 bool using_xfixes_1_0 = false;
static int xfixes_event_base;
static int xfixes_error_base;
static unsigned int caps_lock_mask = 0;
static unsigned int num_lock_mask = 0;
//FIXME: nicify
static const char * const utf8_atom_names[] = {
"UTF8_STRING",
"text/plain;charset=UTF-8",
"text/plain;charset=utf-8",
};
#define utf8_atom_count (sizeof(utf8_atom_names)/sizeof(utf8_atom_names[0]))
struct selection_request {
XEvent event;
selection_request *next;
};
static int expected_targets_notifies = 0;
static bool waiting_for_property_notify = false;
static uint8_t* clipboard_data = NULL;
static int32_t clipboard_data_size = 0;
static int32_t clipboard_data_space = 0;
static Atom clipboard_request_target = None;
static selection_request *next_selection_request = NULL;
static uint32_t clipboard_type_count = 0;
/* TODO Add support for more types here */
/* Warning the size of these 2 needs to be increased each time we add
support for a new type!! */
static uint32_t clipboard_agent_types[1];
static Atom clipboard_x11_targets[1];
static Mutex clipboard_lock;
static Atom clipboard_prop;
static Atom incr_atom;
static Atom utf8_atoms[utf8_atom_count];
static Atom targets_atom;
static Atom multiple_atom;
static Bool handle_x_error = false;
static int x_error_code;
static void handle_selection_request();
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 DefaultDisplayModeListener: public Platform::DisplayModeListener {
public:
void on_display_mode_change() {}
};
static DefaultDisplayModeListener default_display_mode_listener;
static Platform::DisplayModeListener* display_mode_listener = &default_display_mode_listener;
class DefaultClipboardListener: public Platform::ClipboardListener {
public:
void on_clipboard_grab(uint32_t *types, uint32_t type_count) {}
void on_clipboard_request(uint32_t type) {}
void on_clipboard_notify(uint32_t type, uint8_t* data, int32_t size) {}
void on_clipboard_release() {}
};
static DefaultClipboardListener default_clipboard_listener;
static Platform::ClipboardListener* clipboard_listener = &default_clipboard_listener;
static const char *atom_name(Atom atom)
{
if (atom == None)
return "None";
return XGetAtomName(x_display, atom);
}
static uint32_t get_clipboard_type(Atom target) {
int i;
if (target == None)
return VD_AGENT_CLIPBOARD_NONE;
for (i = 0; i < utf8_atom_count; i++)
if (utf8_atoms[i] == target)
return VD_AGENT_CLIPBOARD_UTF8_TEXT;
/* TODO Add support for more types here */
LOG_WARN("unexpected selection type %s", atom_name(target));
return VD_AGENT_CLIPBOARD_NONE;
}
static Atom get_clipboard_target(uint32_t type) {
int i;
for (i = 0; i < clipboard_type_count; i++)
if (clipboard_agent_types[i] == type)
return clipboard_x11_targets[i];
LOG_WARN("client requested unavailable type %u", type);
return None;
}
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) {
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;
}
static void handle_x_errors_start(void)
{
handle_x_error = True;
x_error_code = 0;
}
static int handle_x_errors_stop(void)
{
handle_x_error = False;
return x_error_code;
}
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) {
x_shm_avail = false;
goto err1;
}
shminfo->shmid = shmget(IPC_PRIVATE, height * image->bytes_per_line,
IPC_CREAT | 0777);
if (shminfo->shmid < 0) {
/* EINVAL indicates, most likely, that the segment we asked for
* is bigger than SHMMAX, so we don't treat it as a permanent
* error. ENOSPC and ENOMEM may also indicate this, but
* more likely are permanent errors.
*/
if (errno != EINVAL) {
x_shm_avail = false;
}
goto err2;
}
shminfo->shmaddr = (char *)shmat(shminfo->shmid, 0, 0);
if (!shminfo->shmaddr) {
/* Failure in shmat is almost certainly permanent. Most likely error is
* EMFILE, which would mean that we've exceeded the per-process
* Shm segment limit.
*/
x_shm_avail = false;
goto err2;
}
shminfo->readOnly = False;
if (!XShmAttach(XPlatform::get_display(), shminfo)) {
x_shm_avail = false;
goto err2;
}
handle_x_errors_start();
/* Ensure the xserver has attached the xshm segment */
XSync (XPlatform::get_display(), False);
if (handle_x_errors_stop()) {
x_shm_avail = false;
goto err2;
}
/* Mark segment as released so that it will be destroyed when
the xserver releases the segment. This way we won't leak
the segment if the client crashes. */
shmctl(shminfo->shmid, IPC_RMID, 0);
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];
}
#ifdef USE_OGL
GLXFBConfig** XPlatform::get_fbconfig()
{
return fb_config;
}
#endif // USE_OGL
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 failed");
}
}
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()
{
#ifdef HAVE_CLOCK_GETTIME
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);
#else
struct timeval tv;
gettimeofday(&tv, NULL);
return uint64_t(tv.tv_sec) * 1000 * 1000 * 1000 + uint64_t(tv.tv_usec) * 1000;
#endif
}
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);
XFlush(x_display);
}
void Platform::msleep(unsigned int millisec)
{
usleep(millisec * 1000);
}
void Platform::yield()
{
POSIX_YIELD_FUNC;
}
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(DisplayModeListener* 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;
};
static void intern_clipboard_atoms()
{
int i;
static bool interned = false;
if (interned) return;
clipboard_prop = XInternAtom(x_display, "CLIPBOARD", False);
incr_atom = XInternAtom(x_display, "INCR", False);
multiple_atom = XInternAtom(x_display, "MULTIPLE", False);
targets_atom = XInternAtom(x_display, "TARGETS", False);
for(i = 0; i < utf8_atom_count; i++)
utf8_atoms[i] = XInternAtom(x_display, utf8_atom_names[i], False);
interned = true;
}
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);
//FIXME: replace RootWindow() in other refs as well?
platform_win = XCreateSimpleWindow(display, RootWindow(display, screen), 0, 0, 1, 1, 0, 0, 0);
LOG_INFO("platform_win: %u", (unsigned int)platform_win);
intern_clipboard_atoms();
XSelectInput(display, platform_win, StructureNotifyMask);
XRRSelectInput(display, platform_win, RRScreenChangeNotifyMask);
if (using_xfixes_1_0) {
XFixesSelectSelectionInput(display, platform_win, clipboard_prop,
XFixesSetSelectionOwnerNotifyMask);
}
Monitor::self_monitors_change++;
process_monitor_configure_events(platform_win);
Monitor::self_monitors_change--;
XPlatform::set_win_proc(platform_win, 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 screen 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(platform_win);
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;
}
platform_win = XCreateSimpleWindow(display, RootWindow(display, screen), 0, 0, 1, 1, 0, 0, 0);
LOG_INFO("platform_win: %u", (unsigned int)platform_win);
intern_clipboard_atoms();
XSelectInput(display, platform_win, StructureNotifyMask);
X_DEBUG_SYNC(get_display());
XRRSelectInput(display, platform_win, RRScreenChangeNotifyMask);
X_DEBUG_SYNC(get_display());
if (using_xfixes_1_0) {
XFixesSelectSelectionInput(display, platform_win, clipboard_prop,
XFixesSetSelectionOwnerNotifyMask);
}
XMonitor::inc_change_ref();
process_monitor_configure_events(platform_win);
XMonitor::dec_change_ref();
XPlatform::set_win_proc(platform_win, 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(platform_win);
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();
process_monitor_configure_events(platform_win);
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();
}
XFlush(x_display);
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();
}
XFlush(x_display);
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 ensure_clipboard_data_space(uint32_t size)
{
if (size > clipboard_data_space) {
free(clipboard_data);
clipboard_data = (uint8_t *)malloc(size);
assert(clipboard_data);
clipboard_data_space = size;
}
}
static void send_selection_notify(Atom prop, int process_next_req)
{
XEvent res, *event = &next_selection_request->event;
selection_request *old_request;
res.xselection.property = prop;
res.xselection.type = SelectionNotify;
res.xselection.display = event->xselectionrequest.display;
res.xselection.requestor = event->xselectionrequest.requestor;
res.xselection.selection = event->xselectionrequest.selection;
res.xselection.target = event->xselectionrequest.target;
res.xselection.time = event->xselectionrequest.time;
XSendEvent(x_display, event->xselectionrequest.requestor, 0, 0, &res);
XFlush(x_display);
old_request = next_selection_request;
next_selection_request = next_selection_request->next;
delete old_request;
if (process_next_req)
handle_selection_request();
}
static void print_targets(const char *action, Atom *atoms, int c)
{
int i;
LOG_INFO("%s %d targets:", action, c);
for (i = 0; i < c; i++)
LOG_INFO("%s", atom_name(atoms[i]));
}
static void send_targets(XEvent& request_event)
{
/* TODO Add support for more types here */
/* Warning the size of this needs to be increased each time we add support
for a new type, or the atom count of an existing type changes */
Atom targets[4] = { targets_atom, };
int i, j, target_count = 1;
for (i = 0; i < clipboard_type_count; i++) {
switch (clipboard_agent_types[i]) {
case VD_AGENT_CLIPBOARD_UTF8_TEXT:
for (j = 0; j < utf8_atom_count; j++) {
targets[target_count] = utf8_atoms[j];
target_count++;
}
break;
/* TODO Add support for more types here */
}
}
Window requestor_win = request_event.xselectionrequest.requestor;
Atom prop = request_event.xselectionrequest.property;
if (prop == None)
prop = request_event.xselectionrequest.target;
XChangeProperty(x_display, requestor_win, prop, XA_ATOM, 32,
PropModeReplace, (unsigned char *)&targets,
target_count);
print_targets("sent", targets, target_count);
send_selection_notify(prop, 1);
}
static int get_selection(XEvent &event, Atom type, Atom prop, int format,
unsigned char **data_ret, bool incr)
{
Bool del = incr ? True: False;
Atom type_ret;
int format_ret, ret_val = -1;
unsigned long len, remain;
unsigned char *data = NULL;
if (incr) {
if (event.xproperty.atom != prop) {
LOG_WARN("PropertyNotify parameters mismatch");
goto exit;
}
} else {
if (event.xselection.property == None) {
LOG_INFO("XConvertSelection refused by clipboard owner");
goto exit;
}
if (event.xselection.requestor != platform_win ||
event.xselection.selection != clipboard_prop ||
event.xselection.property != prop) {
LOG_WARN("SelectionNotify parameters mismatch");
goto exit;
}
}
if (XGetWindowProperty(x_display, platform_win, prop, 0,
LONG_MAX, del, type, &type_ret, &format_ret, &len,
&remain, &data) != Success) {
LOG_WARN("XGetWindowProperty failed");
goto exit;
}
if (!incr) {
if (type_ret == incr_atom) {
if (waiting_for_property_notify) {
LOG_WARN("received an incr property notify while still reading another incr property");
goto exit;
}
XSelectInput(x_display, platform_win, PropertyChangeMask);
XDeleteProperty(x_display, platform_win, prop);
XFlush(x_display);
waiting_for_property_notify = true;
ensure_clipboard_data_space(*(uint32_t*)data);
XFree(data);
return 0; /* Wait for more data */
}
XDeleteProperty(x_display, platform_win, prop);
XFlush(x_display);
}
if (type_ret != type) {
LOG_WARN("expected property type: %s, got: %s", atom_name(type),
atom_name(type_ret));
goto exit;
}
if (format_ret != format) {
LOG_WARN("expected %d bit format, got %d bits", format, format_ret);
goto exit;
}
/* Convert len to bytes */
switch(format) {
case 8:
break;
case 16:
len *= sizeof(short);
break;
case 32:
len *= sizeof(long);
break;
}
if (incr) {
if (len) {
if (clipboard_data_size + len > clipboard_data_space) {
clipboard_data_space = clipboard_data_size + len;
clipboard_data = (uint8_t *)realloc(clipboard_data, clipboard_data_space);
assert(clipboard_data);
}
memcpy(clipboard_data + clipboard_data_size, data, len);
clipboard_data_size += len;
LOG_INFO("Appended %d bytes to buffer", len);
XFree(data);
return 0; /* Wait for more data */
}
len = clipboard_data_size;
*data_ret = clipboard_data;
} else
*data_ret = data;
if (len > 0)
ret_val = len;
else
LOG_WARN("property contains no data (zero length)");
exit:
if ((incr || ret_val == -1) && data)
XFree(data);
if (incr) {
clipboard_data_size = 0;
waiting_for_property_notify = false;
}
return ret_val;
}
static void get_selection_free(unsigned char *data, bool incr)
{
if (incr) {
/* If the clipboard was large return the memory to the system */
if (clipboard_data_space > 512 * 1024) {
free(clipboard_data);
clipboard_data = NULL;
clipboard_data_space = 0;
}
} else if (data)
XFree(data);
}
static Atom atom_lists_overlap(Atom *atoms1, Atom *atoms2, int l1, int l2)
{
int i, j;
for (i = 0; i < l1; i++)
for (j = 0; j < l2; j++)
if (atoms1[i] == atoms2[j])
return atoms1[i];
return 0;
}
static void handle_targets_notify(XEvent& event, bool incr)
{
int len;
Lock lock(clipboard_lock);
Atom *atoms = NULL;
if (!expected_targets_notifies) {
LOG_WARN("unexpected selection notify TARGETS");
return;
}
expected_targets_notifies--;
/* If we have more targets_notifies pending, ignore this one, we
are only interested in the targets list of the current owner
(which is the last one we've requested a targets list from) */
if (expected_targets_notifies)
return;
len = get_selection(event, XA_ATOM, targets_atom, 32,
(unsigned char **)&atoms, incr);
if (len == 0 || len == -1) /* waiting for more data or error? */
return;
/* bytes -> atoms */
len /= sizeof(Atom);
print_targets("received", atoms, len);
clipboard_type_count = 0;
Atom atom = atom_lists_overlap(utf8_atoms, atoms, utf8_atom_count, len);
if (atom) {
clipboard_agent_types[clipboard_type_count] =
VD_AGENT_CLIPBOARD_UTF8_TEXT;
clipboard_x11_targets[clipboard_type_count] = atom;
clipboard_type_count++;
}
/* TODO Add support for more types here */
if (clipboard_type_count)
clipboard_listener->on_clipboard_grab(clipboard_agent_types,
clipboard_type_count);
get_selection_free((unsigned char *)atoms, incr);
}
static void handle_selection_notify(XEvent& event, bool incr)
{
int len = -1;
uint32_t type = get_clipboard_type(clipboard_request_target);
unsigned char *data = NULL;
if (clipboard_request_target == None)
LOG_INFO("SelectionNotify received without a target");
else if (!incr && event.xselection.target != clipboard_request_target)
LOG_WARN("Requested %s target got %s",
atom_name(clipboard_request_target),
atom_name(event.xselection.target));
else
len = get_selection(event, clipboard_request_target, clipboard_prop,
8, &data, incr);
if (len == 0) /* waiting for more data? */
return;
if (len == -1) {
type = VD_AGENT_CLIPBOARD_NONE;
len = 0;
}
clipboard_listener->on_clipboard_notify(type, data, len);
clipboard_request_target = None;
get_selection_free(data, incr);
}
static void handle_selection_request()
{
XEvent *event;
uint32_t type = VD_AGENT_CLIPBOARD_NONE;
if (!next_selection_request)
return;
event = &next_selection_request->event;
if (Platform::get_clipboard_owner() != Platform::owner_guest) {
LOG_INFO("received selection request event for target %s, "
"while clipboard not owned by guest",
atom_name(event->xselectionrequest.target));
send_selection_notify(None, 1);
return;
}
if (event->xselectionrequest.target == multiple_atom) {
LOG_WARN("multiple target not supported");
send_selection_notify(None, 1);
return;
}
if (event->xselectionrequest.target == targets_atom) {
send_targets(*event);
return;
}
type = get_clipboard_type(event->xselectionrequest.target);
if (type == VD_AGENT_CLIPBOARD_NONE) {
send_selection_notify(None, 1);
return;
}
clipboard_listener->on_clipboard_request(type);
}
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();
return;
}
if (event.type == XFixesSelectionNotify + xfixes_event_base) {
XFixesSelectionNotifyEvent* selection_event = (XFixesSelectionNotifyEvent *)&event;
if (selection_event->subtype != XFixesSetSelectionOwnerNotify) {
LOG_INFO("Unsupported selection event %u", selection_event->subtype);
return;
}
LOG_INFO("XFixesSetSelectionOwnerNotify %u",
(unsigned int)selection_event->owner);
/* Ignore becoming the owner ourselves */
if (selection_event->owner == platform_win)
return;
/* If the clipboard owner is changed we no longer own it */
Platform::set_clipboard_owner(Platform::owner_none);
if (selection_event->owner == None)
return;
/* Request the supported targets from the new owner */
XConvertSelection(x_display, clipboard_prop, targets_atom,
targets_atom, platform_win, CurrentTime);
XFlush(x_display);
expected_targets_notifies++;
return;
}
switch (event.type) {
case SelectionRequest: {
Lock lock(clipboard_lock);
struct selection_request *req, *new_req;
new_req = new selection_request;
assert(new_req);
new_req->event = event;
new_req->next = NULL;
if (!next_selection_request) {
next_selection_request = new_req;
handle_selection_request();
break;
}
/* maybe we should limit the selection_request stack depth ? */
req = next_selection_request;
while (req->next)
req = req->next;
req->next = new_req;
break;
}
case SelectionClear:
/* Do nothing the clipboard ownership will get updated through
the XFixesSetSelectionOwnerNotify event */
break;
case SelectionNotify:
if (event.xselection.target == targets_atom)
handle_targets_notify(event, false);
else
handle_selection_notify(event, false);
break;
case PropertyNotify:
if (!waiting_for_property_notify || event.xproperty.state != PropertyNewValue) {
break;
}
if (event.xproperty.atom == targets_atom)
handle_targets_notify(event, true);
else
handle_selection_notify(event, true);
break;
default:
return;
}
}
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;
}
#ifdef USE_OGL
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;
}
#endif // USE_OGL
}
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);
}
static void init_xfixes()
{
int major;
int minor;
using_xfixes_1_0 = XFixesQueryExtension(x_display, &xfixes_event_base, &xfixes_error_base) &&
XFixesQueryVersion(x_display, &major, &minor) && major >= 1;
}
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];
if (handle_x_error) {
if (error_event->error_code) {
x_error_code = error_event->error_code;
}
return 0;
}
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()
{
#ifdef USE_OGL
int err, ev;
#endif // USE_OGL
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));
screen_format = new RedDrawable::Format[ScreenCount(x_display)];
memset(screen_format, 0, sizeof(RedDrawable::Format) * ScreenCount(x_display));
#ifdef USE_OGL
fb_config = new GLXFBConfig *[ScreenCount(x_display)];
memset(fb_config, 0, sizeof(GLXFBConfig *) * 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
#else // !USE_OGL
{
for (int i = 0; i < ScreenCount(x_display); ++i) {
vinfo[i] = get_x_vis_info(i);
}
}
#endif // USE_OGL
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_xfixes();
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);
XFlush(x_display);
}
return;
case X11_NUM_LOCK_LED:
if (num_lock_mask) {
XkbLockModifiers(x_display, XkbUseCoreKbd, num_lock_mask, set ? num_lock_mask : 0);
XFlush(x_display);
}
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);
XFlush(x_display);
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);
XFlush(x_display);
}
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);
XFlush(x_display);
}
}
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 cursor 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();
}
bool Platform::on_clipboard_grab(uint32_t *types, uint32_t type_count)
{
Lock lock(clipboard_lock);
int i;
clipboard_type_count = 0;
for (i = 0; i < type_count; i++) {
/* TODO Add support for more types here */
/* Check if we support the type */
if (types[i] != VD_AGENT_CLIPBOARD_UTF8_TEXT)
continue;
clipboard_agent_types[clipboard_type_count] = types[i];
clipboard_type_count++;
}
if (!clipboard_type_count) {
LOG_INFO("No supported clipboard types in agent grab");
return false;
}
XSetSelectionOwner(x_display, clipboard_prop, platform_win, CurrentTime);
XFlush(x_display);
set_clipboard_owner_unlocked(owner_guest);
return true;
}
int Platform::_clipboard_owner = Platform::owner_none;
void Platform::set_clipboard_owner(int new_owner)
{
Lock lock(clipboard_lock);
set_clipboard_owner_unlocked(new_owner);
}
void Platform::set_clipboard_owner_unlocked(int new_owner)
{
const char * const owner_str[] = { "none", "guest", "client" };
/* Clear pending requests and clipboard data */
{
if (next_selection_request) {
LOG_INFO("selection requests pending upon clipboard owner change, clearing");
while (next_selection_request)
send_selection_notify(None, 0);
}
clipboard_data_size = 0;
clipboard_request_target = None;
waiting_for_property_notify = false;
/* Clear cached clipboard type info when there is no new owner
(otherwise the new owner will already have set new type info) */
if (new_owner == owner_none)
clipboard_type_count = 0;
}
if (new_owner == owner_none)
clipboard_listener->on_clipboard_release();
_clipboard_owner = new_owner;
LOG_INFO("new clipboard owner: %s", owner_str[new_owner]);
}
void Platform::set_clipboard_listener(ClipboardListener* listener)
{
clipboard_listener = listener ? listener : &default_clipboard_listener;
}
bool Platform::on_clipboard_notify(uint32_t type, const uint8_t* data, int32_t size)
{
Lock lock(clipboard_lock);
Atom prop;
XEvent *event;
uint32_t type_from_event;
if (!next_selection_request) {
LOG_INFO("received clipboard data without an outstanding"
"selection request, ignoring");
return true;
}
if (type == VD_AGENT_CLIPBOARD_NONE) {
send_selection_notify(None, 1);
return true;
}
event = &next_selection_request->event;
type_from_event = get_clipboard_type(event->xselectionrequest.target);
if (type_from_event != type) {
LOG_WARN("expecting type %u clipboard data got %u",
type_from_event, type);
send_selection_notify(None, 1);
return false;
}
prop = event->xselectionrequest.property;
if (prop == None)
prop = event->xselectionrequest.target;
/* FIXME: use INCR for large data transfers */
XChangeProperty(x_display, event->xselectionrequest.requestor, prop,
event->xselectionrequest.target, 8, PropModeReplace,
data, size);
send_selection_notify(prop, 1);
return true;
}
bool Platform::on_clipboard_request(uint32_t type)
{
Lock lock(clipboard_lock);
Atom target = get_clipboard_target(type);
if (target == None)
return false;
if (XGetSelectionOwner(x_display, clipboard_prop) == None) {
LOG_INFO("No owner for the selection");
return false;
}
if (clipboard_request_target) {
LOG_INFO("XConvertSelection request is already pending");
return false;
}
clipboard_request_target = target;
XConvertSelection(x_display, clipboard_prop, target, clipboard_prop, platform_win, CurrentTime);
XFlush(x_display);
return true;
}
void Platform::on_clipboard_release()
{
XEvent event;
if (XGetSelectionOwner(x_display, clipboard_prop) != platform_win) {
LOG_INFO("Platform::on_clipboard_release() called while not selection owner");
return;
}
/* Note there is a small race window here where another x11 app could
acquire selection ownership and we kick it off again, nothing we
can do about that :( */
XSetSelectionOwner(x_display, clipboard_prop, None, CurrentTime);
/* Make sure we process the XFixesSetSelectionOwnerNotify event caused
by this, so we don't end up changing the clipboard owner to none, after
it has already been re-owned because this event is still pending. */
XSync(x_display, False);
while (XCheckTypedEvent(x_display,
XFixesSelectionNotify + xfixes_event_base,
&event))
root_win_proc(event);
/* Note no need to do a set_clipboard_owner(owner_none) here, as that is
already done by processing the XFixesSetSelectionOwnerNotify event. */
}