/*
Copyright (C) 2009 Red Hat, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. 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 "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 "events_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 XVisualInfo **vinfo = NULL;
static GLXFBConfig **fb_config;
static EventsLoop events_loop;
static XContext win_proc_context;
static bool quit_request = false;
static pthread_t main_thread;
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)
{
return (ListenerRef)(new LinuxListener(name, listener_interface, events_loop));
}
void NamedPipe::destroy(ListenerRef listener_ref)
{
delete (LinuxListener *)listener_ref;
}
void NamedPipe::destroy_connection(ConnectionRef conn_ref)
{
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 EventsLoop::File {
public:
XEventHandler(Display& x_display);
virtual void on_event() {}
virtual int get_fd() {return _x_fd;}
private:
int _x_fd;
};
XEventHandler::XEventHandler(Display& x_display)
{
if ((_x_fd = ConnectionNumber(&x_display)) == -1) {
THROW("get x fd failed");
}
}
class WakeupEventHandler: public EventsLoop::Trigger {
public:
virtual void on_event() {}
};
static WakeupEventHandler wakeup_handler;
#define NSEC_PER_SEC (1000 * 1000 * 1000)
class Timer {
public:
Timer(timer_proc_t proc, void* opaque);
~Timer();
void arm(uint32_t msec);
void disarm();
bool action(const timespec& now);
const timespec& get_experatoin() const { return _experatoin;}
static int get_timout();
static void timers_action();
private:
void calc_next_experatoin_time();
static bool timespec_less(const timespec& time, const timespec& from);
static bool timespec_equal(const timespec& time, const timespec& from);
static bool timespec_less_or_equal(const timespec& time, const timespec& from);
public:
static RecurciveMutex timers_lock;
private:
timer_proc_t _proc;
void* _opaque;
bool _armed;
timespec _interval;
timespec _experatoin;
class Compare {
public:
bool operator () (const Timer* timer1, const Timer* timer2) const
{
if (!Timer::timespec_less(timer1->get_experatoin(), timer2->get_experatoin())) {
return Timer::timespec_equal(timer1->get_experatoin(), timer2->get_experatoin()) ?
timer1 < timer2 : false;
}
return true;
}
};
typedef std::set TimersSet;
static TimersSet armed_timers;
};
Timer::TimersSet Timer::armed_timers;
RecurciveMutex Timer::timers_lock;
Display* XPlatform::get_display()
{
return x_display;
}
XVisualInfo** XPlatform::get_vinfo()
{
return vinfo;
}
GLXFBConfig** XPlatform::get_fbconfig()
{
return fb_config;
}
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()
{
quit_request = true;
wakeup();
}
void Platform::wait_events()
{
ASSERT(pthread_self() == main_thread);
XFlush(x_display);
if (!XPending(x_display)) {
events_loop.run_once(Timer::get_timout());
Timer::timers_action();
}
}
void Platform::wakeup()
{
wakeup_handler.trigger();
}
bool Platform::process_events()
{
ASSERT(pthread_self() == main_thread);
while (XPending(x_display)) {
XPointer proc_pointer;
XEvent event;
XNextEvent(x_display, &event);
if (event.xany.window == None) {
LOG_WARN("invalid window");
continue;
}
if (XFindContext(x_display, event.xany.window, win_proc_context, &proc_pointer)) {
THROW("no window proc");
}
((XPlatform::win_proc_t)proc_pointer)(event);
}
return quit_request;
}
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/";
}
void Platform::msleep(unsigned int millisec)
{
usleep(millisec * 1000);
}
void Platform::yield()
{
pthread_yield();
}
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));
}
}
Timer::Timer(timer_proc_t proc, void* opaque)
: _proc (proc)
, _opaque (opaque)
, _armed (false)
{
}
Timer::~Timer()
{
disarm();
}
void Timer::arm(uint32_t msec)
{
disarm();
_interval.tv_sec = msec / 1000;
_interval.tv_nsec = (msec % 1000) * 1000 * 1000;
if (clock_gettime(CLOCK_MONOTONIC, &_experatoin)) {
THROW("gettime failed %s", strerror(errno));
}
calc_next_experatoin_time();
_armed = true;
armed_timers.insert(this);
}
void Timer::disarm()
{
if (!_armed) {
return;
}
armed_timers.erase(this);
_armed = false;
}
#define TINER_COMPENSATION
bool Timer::action(const timespec& now)
{
ASSERT(_armed);
ASSERT(now.tv_nsec < NSEC_PER_SEC);
if (timespec_less(now, _experatoin)) {
return false;
}
armed_timers.erase(this);
#ifndef TINER_COMPENSATION
_experatoin = now;
#endif
calc_next_experatoin_time();
#ifdef TINER_COMPENSATION
if (timespec_less_or_equal(_experatoin, now)) {
_experatoin = now;
calc_next_experatoin_time();
}
#endif
armed_timers.insert(this);
_proc(_opaque, (TimerID)this);
return true;
}
int Timer::get_timout()
{
RecurciveLock lock(Timer::timers_lock);
TimersSet::iterator iter;
iter = armed_timers.begin();
if (iter == armed_timers.end()) {
return -1;
}
timespec now;
if (clock_gettime(CLOCK_MONOTONIC, &now)) {
THROW("gettime failed %s", strerror(errno));
}
const timespec& next_time = (*iter)->get_experatoin();
if (!timespec_less(now, next_time)) {
return 0;
}
return ((next_time.tv_nsec - now.tv_nsec) / 1000 / 1000) +
(next_time.tv_sec - now.tv_sec) * 1000;
}
void Timer::timers_action()
{
RecurciveLock lock(timers_lock);
timespec now;
if (clock_gettime(CLOCK_MONOTONIC, &now)) {
THROW("gettime failed %s", strerror(errno));
}
TimersSet::iterator iter;
while ((iter = armed_timers.begin()) != armed_timers.end() && (*iter)->action(now));
}
void Timer::calc_next_experatoin_time()
{
_experatoin.tv_nsec += _interval.tv_nsec;
_experatoin.tv_sec += (_experatoin.tv_nsec / NSEC_PER_SEC) + _interval.tv_sec;
_experatoin.tv_nsec %= NSEC_PER_SEC;
}
bool Timer::timespec_less(const timespec& time, const timespec& from)
{
return time.tv_sec < from.tv_sec || (time.tv_sec == from.tv_sec && time.tv_nsec < from.tv_nsec);
}
bool Timer::timespec_equal(const timespec& time, const timespec& from)
{
return time.tv_sec == from.tv_sec && time.tv_nsec <= from.tv_nsec;
}
bool Timer::timespec_less_or_equal(const timespec& time, const timespec& from)
{
return time.tv_sec < from.tv_sec ||
(time.tv_sec == from.tv_sec && time.tv_nsec <= from.tv_nsec);
}
TimerID Platform::create_interval_timer(timer_proc_t proc, void* opaque)
{
return (TimerID) new Timer(proc, opaque);
}
bool Platform::activate_interval_timer(TimerID timer, unsigned int millisec)
{
RecurciveLock lock(Timer::timers_lock);
((Timer*)timer)->arm(millisec);
if (pthread_self() != main_thread) {
wakeup();
}
return true;
}
bool Platform::deactivate_interval_timer(TimerID timer)
{
RecurciveLock lock(Timer::timers_lock);
((Timer*)timer)->disarm();
return true;
}
void Platform::destroy_interval_timer(TimerID timer)
{
if (timer == INVALID_TIMER) {
return;
}
RecurciveLock lock(Timer::timers_lock);
delete (Timer*)timer;
}
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;}
Point get_position() const {return _position;}
private:
Display* _display;
int _screen;
Point _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 void set_mode(int width, int height)
{
_out_of_sync = width > get_width() || height > get_height();
}
virtual void restore() {}
virtual int get_depth() { return XPlatform::get_vinfo()[0]->depth;}
virtual Point get_position() { return XScreen::get_position();}
virtual Point get_size() const { Point 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();}
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 void set_mode(int width, int height);
virtual void restore();
virtual int get_depth() { return XPlatform::get_vinfo()[0]->depth;}
virtual Point get_position() { return XScreen::get_position();}
virtual Point get_size() const { Point 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();}
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::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::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);
Point get_trans_top_left();
Point 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 void set_mode(int width, int height);
virtual void restore();
virtual int get_depth();
virtual Point get_position();
virtual Point 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 Rect& get_prev_area();
Rect& 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--;}
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;
Point _position;
Point _size;
RRMode _mode;
Rotation _rotation;
int _noutput;
RROutput* _outputs;
Point _saved_position;
Point _saved_size;
RRMode _saved_mode;
Rotation _saved_rotation;
bool _out_of_sync;
RedScreenRotation _red_rotation;
RedSubpixelOrder _subpixel_order;
int _trans_depth;
Rect _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;
Point pos = mon->get_position();
Point 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());
}
Point MultyMonScreen::get_trans_top_left()
{
Point position;
position.y = position.x = MAXINT;
XMonitorsList::iterator iter = _monitors.begin();
for (; iter != _monitors.end(); iter++) {
Rect& area = (*iter)->get_trans_area();
position.x = MIN(position.x, area.left);
position.y = MIN(position.y, area.top);
}
return position;
}
Point MultyMonScreen::get_trans_bottom_right()
{
Point position;
position.y = position.x = MININT;
XMonitorsList::iterator iter = _monitors.begin();
for (; iter != _monitors.end(); iter++) {
Rect& 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);
Point 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 Rect& base, int size, Rect& area)
static int get_push_distance(const Rect& fix_area, const Rect& other)
static int get_head(const Rect& area)
static int get_tail(const Rect& area)
static void move_head(Rect& area, int delta)
static int get_pull_distance(const Rect& fix_area, const Rect& other)
static void offset(Rect& area, int delta)
static void shrink(Rect& area, int delta)
static int get_distance(const Rect& area, const Rect& other_area)
static bool is_on_tail(const Rect& area, const Rect& other_area)
static bool is_on_perpendiculars(const Rect& area, const Rect& 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 Rect& base, int size, Rect& area)
{
area.right = base.left;
area.left = area.right - size;
area.bottom = base.bottom;
area.top = base.top;
}
static int get_push_distance(const Rect& fix_area, const Rect& other)
{
return other.right - fix_area.left;
}
static int get_head(const Rect& area)
{
return area.left;
}
static int get_tail(const Rect& area)
{
return area.right;
}
static void move_head(Rect& area, int delta)
{
area.left -= delta;
ASSERT(area.right >= area.left);
}
static int get_pull_distance(const Rect& fix_area, const Rect& other)
{
return other.left - fix_area.right;
}
static void offset(Rect& area, int delta)
{
rect_offset(area, -delta, 0);
}
static void shrink(Rect& area, int delta)
{
area.right -= delta;
ASSERT(area.right > area.left);
}
static int get_distance(const Rect& area, const Rect& other_area)
{
return other_area.left - area.left;
}
static bool is_on_tail(const Rect& area, const Rect& other_area)
{
return area.right == other_area.left && other_area.top < area.bottom &&
other_area.bottom > area.top;
}
static bool is_on_perpendiculars(const Rect& area, const Rect& 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 Rect& base, int size, Rect& area)
{
area.left = base.right;
area.right = area.left + size;
area.top = base.top;
area.bottom = base.bottom;
}
static int get_push_distance(const Rect& fix_area, const Rect& other)
{
return fix_area.right - other.left;
}
static int get_head(const Rect& area)
{
return area.right;
}
static int get_tail(const Rect& area)
{
return area.left;
}
static void move_head(Rect& area, int delta)
{
area.right += delta;
ASSERT(area.right >= area.left);
}
static int get_pull_distance(const Rect& fix_area, const Rect& other)
{
return fix_area.left - other.right;
}
static void offset(Rect& area, int delta)
{
rect_offset(area, delta, 0);
}
static bool is_on_tail(const Rect& area, const Rect& other_area)
{
return other_area.right == area.left && other_area.top < area.bottom &&
other_area.bottom > area.top;
}
static bool is_on_perpendiculars(const Rect& area, const Rect& 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 Rect& base, int size, Rect& area)
{
area.left = base.left;
area.right = base.right;
area.bottom = base.top;
area.top = area.bottom - size;
}
static int get_push_distance(const Rect& fix_area, const Rect& other)
{
return other.bottom - fix_area.top;
}
static int get_head(const Rect& area)
{
return area.top;
}
static int get_tail(const Rect& area)
{
return area.bottom;
}
static void move_head(Rect& area, int delta)
{
area.top -= delta;
ASSERT(area.bottom >= area.top);
}
static int get_pull_distance(const Rect& fix_area, const Rect& other)
{
return other.top - fix_area.bottom;
}
static void offset(Rect& area, int delta)
{
rect_offset(area, 0, -delta);
}
static void shrink(Rect& area, int delta)
{
area.bottom -= delta;
ASSERT(area.bottom > area.top);
}
static int get_distance(const Rect& area, const Rect& other_area)
{
return other_area.top - area.top;
}
static bool is_on_tail(const Rect& area, const Rect& other_area)
{
return area.bottom == other_area.top && other_area.left < area.right &&
other_area.right > area.left;
}
static bool is_on_perpendiculars(const Rect& area, const Rect& 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 Rect& base, int size, Rect& area)
{
area.left = base.left;
area.right = base.right;
area.top = base.bottom;
area.bottom = area.top + size;
}
static int get_push_distance(const Rect& fix_area, const Rect& other)
{
return fix_area.bottom - other.top;
}
static int get_head(const Rect& area)
{
return area.bottom;
}
static int get_tail(const Rect& area)
{
return area.top;
}
static void move_head(Rect& area, int delta)
{
area.bottom += delta;
ASSERT(area.bottom >= area.top);
}
static int get_pull_distance(const Rect& fix_area, const Rect& other)
{
return fix_area.top - other.bottom;
}
static void offset(Rect& area, int delta)
{
rect_offset(area, 0, delta);
}
static bool is_on_tail(const Rect& area, const Rect& other_area)
{
return other_area.bottom == area.top && other_area.left < area.right &&
other_area.right > area.left;
}
static bool is_on_perpendiculars(const Rect& area, const Rect& 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++) {
Rect& 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);
}
Rect 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 Rect& 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 Rect& area = monitor.get_prev_area();
for (iter = monitors.begin(); iter != monitors.end(); iter++) {
if ((*iter)->is_pinned()) {
continue;
}
const Rect& 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 Rect& area = monitor.get_trans_area();
for (XMonitorsList::const_iterator iter = monitors.begin(); iter != monitors.end(); iter++) {
const Rect& 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 Rect 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 Rect& 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);
}
Rect 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 Rect& 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;
}
Point 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) {
_subpixel_order = RED_SUBPIXEL_ORDER_UNKNOWN;
return;
}
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::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::restore()
{
if (!mode_changed()) {
return;
}
set_mode(_saved_size.x, _saved_size.y);
}
int XMonitor::get_depth()
{
return XPlatform::get_vinfo()[0]->depth;
}
Point XMonitor::get_position()
{
return _position;
}
Point 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 Rect& XMonitor::get_prev_area()
{
return _trans_area[_trans_depth - 1];
}
Rect& 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;
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);
}
return monitors;
}
void Platform::destroy_monitors()
{
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);
}
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) {
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 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;
XEventHandler *x_event_handler;
DBG(0, "");
threads_enable = XInitThreads();
main_thread = pthread_self();
if (!(x_display = XOpenDisplay(NULL))) {
THROW("open X display failed");
}
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));
// working with KDE and visual from glXGetVisualFromFBConfig is not working
// well. for now disabling OGL.
if (0 && 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) {
if (!(fb_config[i] = glXChooseFBConfig(x_display, i, attrlist, &num_configs))) {
vinfo[i] = get_x_vis_info(i);
} else {
ASSERT(num_configs > 0);
vinfo[i] = glXGetVisualFromFBConfig(x_display, fb_config[i][0]);
}
if (!vinfo[i]) {
THROW("XGetVisualInfo failed");
}
}
} else {
for (int i = 0; i < ScreenCount(x_display); ++i) {
if (!(vinfo[i] = get_x_vis_info(i))) {
THROW("XGetVisualInfo failed");
}
}
}
XSetErrorHandler(x_error_handler);
XSetIOErrorHandler(x_io_error_handler);
win_proc_context = XUniqueContext();
init_kbd();
init_xrandr();
init_xrender();
x_event_handler = new XEventHandler(*x_display);
events_loop.add_file(*x_event_handler);
events_loop.add_trigger(wakeup_handler);
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);
}
uint32_t Platform::get_keyboard_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_modifiers(uint32_t modifiers)
{
uint32_t now = get_keyboard_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));
}
}
WaveRecordAbstract* Platform::create_recorder(RecordClinet& 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 CursorHeader& 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 CURSOR_TYPE_ALPHA:
break;
case 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 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 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 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 CURSOR_TYPE_COLOR24:
case 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 == 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();
}