x.cpp 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. #include "x.hpp"
  2. slop::XEngine* xengine = new slop::XEngine();
  3. static Bool isDestroyNotify( Display* dpy, XEvent* ev, XPointer win ) {
  4. return ev->type == DestroyNotify && ev->xdestroywindow.window == *((Window*)win);
  5. }
  6. int slop::XEngineErrorHandler( Display* dpy, XErrorEvent* event ) {
  7. // Ignore XGrabKeyboard BadAccess errors, we can work without it.
  8. // 31 = XGrabKeyboard's request code
  9. if ( event->request_code == 31 && event->error_code == BadAccess ) {
  10. return 0;
  11. }
  12. // Everything else should be fatal as I don't like undefined behavior.
  13. char buffer[1024];
  14. XGetErrorText( dpy, event->error_code, buffer, 1024 );
  15. fprintf( stderr,
  16. "_X Error of failed request: %s\n_ Major opcode of failed request: % 3d\n_ Serial number of failed request:% 5d\n_ Current serial number in output stream:?????\n",
  17. buffer,
  18. event->request_code,
  19. event->serial );
  20. exit(1);
  21. }
  22. slop::XEngine::XEngine() {
  23. m_display = NULL;
  24. m_visual = NULL;
  25. m_screen = NULL;
  26. m_good = false;
  27. m_mousex = -1;
  28. m_mousey = -1;
  29. m_hoverWindow = None;
  30. }
  31. slop::XEngine::~XEngine() {
  32. if ( !m_good ) {
  33. return;
  34. }
  35. for ( unsigned int i=0; i<m_cursors.size(); i++ ) {
  36. if ( m_cursors.at( i ) ) {
  37. XFreeCursor( m_display, m_cursors[i] );
  38. }
  39. }
  40. for ( unsigned int i=0; i<m_rects.size(); i++ ) {
  41. delete m_rects.at( i );
  42. }
  43. XCloseDisplay( m_display );
  44. }
  45. // We need to keep track of the rectangle windows, so that they don't override our "focus"d windows.
  46. void slop::XEngine::addRect( Rectangle* rect ) {
  47. m_rects.push_back( rect );
  48. }
  49. void slop::XEngine::removeRect( Rectangle* rect ) {
  50. for ( unsigned int i=0; i<m_rects.size(); i++ ) {
  51. if ( m_rects.at( i ) == rect ) {
  52. m_rects.erase( m_rects.begin() + i );
  53. i--;
  54. delete rect;
  55. return;
  56. }
  57. }
  58. }
  59. bool slop::XEngine::mouseDown( unsigned int button ) {
  60. if ( button >= m_mouse.size() ) {
  61. return false;
  62. }
  63. return m_mouse.at( button );
  64. }
  65. int slop::XEngine::init( std::string display ) {
  66. // Initialize display
  67. m_display = XOpenDisplay( display.c_str() );
  68. if ( !m_display ) {
  69. fprintf( stderr, "Error: Failed to open X display %s\n", display.c_str() );
  70. return 1;
  71. }
  72. m_screen = ScreenOfDisplay( m_display, DefaultScreen( m_display ) );
  73. m_visual = DefaultVisual ( m_display, XScreenNumberOfScreen( m_screen ) );
  74. m_colormap = DefaultColormap( m_display, XScreenNumberOfScreen( m_screen ) );
  75. //m_root = RootWindow ( m_display, XScreenNumberOfScreen( m_screen ) );
  76. m_root = DefaultRootWindow( m_display );
  77. m_good = true;
  78. XSetErrorHandler( slop::XEngineErrorHandler );
  79. return 0;
  80. }
  81. bool slop::XEngine::anyKeyPressed() {
  82. if ( !m_good ) {
  83. return false;
  84. }
  85. // Thanks to SFML for some reliable key state grabbing.
  86. // Get the whole keyboard state
  87. char keys[ 32 ];
  88. XQueryKeymap( m_display, keys );
  89. // Each bit indicates a different key, 1 for pressed, 0 otherwise.
  90. // Every bit should be 0 if nothing is pressed.
  91. for ( unsigned int i = 0; i < 32; i++ ) {
  92. if ( keys[ i ] != 0 ) {
  93. return true;
  94. }
  95. }
  96. return false;
  97. }
  98. int slop::XEngine::grabKeyboard() {
  99. if ( !m_good ) {
  100. return 1;
  101. }
  102. XGrabKeyboard( m_display, m_root, False, GrabModeAsync, GrabModeAsync, CurrentTime );
  103. return 0;
  104. }
  105. int slop::XEngine::releaseKeyboard() {
  106. if ( !m_good ) {
  107. return 1;
  108. }
  109. XUngrabKeyboard( m_display, CurrentTime );
  110. return 0;
  111. }
  112. // Grabs the cursor, be wary that setCursor changes the mouse masks.
  113. int slop::XEngine::grabCursor( slop::CursorType type ) {
  114. if ( !m_good ) {
  115. return 1;
  116. }
  117. int xfontcursor = getCursor( type );
  118. int err = XGrabPointer( m_display, m_root, False,
  119. PointerMotionMask | ButtonPressMask | ButtonReleaseMask,
  120. GrabModeAsync, GrabModeAsync, m_root, xfontcursor, CurrentTime );
  121. if ( err != GrabSuccess ) {
  122. fprintf( stderr, "Error: Failed to grab X cursor.\n" );
  123. fprintf( stderr, "This can be caused by launching slop incorrectly.\n" );
  124. fprintf( stderr, "gnome-session launches it fine from keyboard binds.\n" );
  125. return 1;
  126. }
  127. // Quickly set the mouse position so we don't have to worry about x11 generating an event.
  128. Window root, child;
  129. int mx, my;
  130. int wx, wy;
  131. unsigned int mask;
  132. XQueryPointer( m_display, m_root, &root, &child, &mx, &my, &wx, &wy, &mask );
  133. m_mousex = mx;
  134. m_mousey = my;
  135. // Oh and while we're at it, make sure we set the window we're hoving over as well.
  136. updateHoverWindow( child );
  137. return 0;
  138. }
  139. int slop::XEngine::releaseCursor() {
  140. if ( !m_good ) {
  141. return 1;
  142. }
  143. XUngrabPointer( m_display, CurrentTime );
  144. return 0;
  145. }
  146. void slop::XEngine::tick() {
  147. if ( !m_good ) {
  148. return;
  149. }
  150. XFlush( m_display );
  151. XEvent event;
  152. while ( XPending( m_display ) ) {
  153. XNextEvent( m_display, &event );
  154. switch ( event.type ) {
  155. case MotionNotify: {
  156. m_mousex = event.xmotion.x;
  157. m_mousey = event.xmotion.y;
  158. break;
  159. }
  160. case ButtonPress: {
  161. // Our pitiful mouse manager--
  162. if ( m_mouse.size() > event.xbutton.button ) {
  163. m_mouse.at( event.xbutton.button ) = true;
  164. } else {
  165. m_mouse.resize( event.xbutton.button+2, false );
  166. m_mouse.at( event.xbutton.button ) = true;
  167. }
  168. break;
  169. }
  170. case ButtonRelease: {
  171. if ( m_mouse.size() > event.xbutton.button ) {
  172. m_mouse.at( event.xbutton.button ) = false;
  173. } else {
  174. m_mouse.resize( event.xbutton.button+2, false );
  175. m_mouse.at( event.xbutton.button ) = false;
  176. }
  177. break;
  178. }
  179. // Due to X11 really hating applications grabbing the keyboard, we use XQueryKeymap to check for downed keys elsewhere.
  180. case KeyPress: {
  181. break;
  182. }
  183. case KeyRelease: {
  184. break;
  185. }
  186. default: break;
  187. }
  188. }
  189. // Since I couldn't get Xlib to send EnterNotify or LeaveNotify events, we need to query the underlying window every frame.
  190. updateHoverWindow();
  191. }
  192. // This converts an enum into a preallocated cursor, the cursor will automatically deallocate itself on ~XEngine
  193. Cursor slop::XEngine::getCursor( slop::CursorType type ) {
  194. int xfontcursor;
  195. switch ( type ) {
  196. default:
  197. case Left: xfontcursor = XC_left_ptr; break;
  198. case Crosshair: xfontcursor = XC_crosshair; break;
  199. case Cross: xfontcursor = XC_cross; break;
  200. case UpperLeftCorner: xfontcursor = XC_ul_angle; break;
  201. case UpperRightCorner: xfontcursor = XC_ur_angle; break;
  202. case LowerLeftCorner: xfontcursor = XC_ll_angle; break;
  203. case LowerRightCorner: xfontcursor = XC_lr_angle; break;
  204. }
  205. Cursor newcursor = 0;
  206. if ( m_cursors.size() > xfontcursor ) {
  207. newcursor = m_cursors.at( xfontcursor );
  208. }
  209. if ( !newcursor ) {
  210. newcursor = XCreateFontCursor( m_display, xfontcursor );
  211. m_cursors.resize( xfontcursor+2, 0 );
  212. m_cursors.at( xfontcursor ) = newcursor;
  213. }
  214. return newcursor;
  215. }
  216. // Swaps out the current cursor, bewary that XChangeActivePointerGrab also resets masks, so if you change the mouse masks on grab you need to change them here too.
  217. void slop::XEngine::setCursor( slop::CursorType type ) {
  218. if ( !m_good ) {
  219. return;
  220. }
  221. Cursor xfontcursor = getCursor( type );
  222. XChangeActivePointerGrab( m_display,
  223. PointerMotionMask | ButtonPressMask | ButtonReleaseMask,
  224. xfontcursor, CurrentTime );
  225. }
  226. void slop::WindowRectangle::setGeometry( Window win, bool decorations ) {
  227. Window junk;
  228. if ( decorations ) {
  229. unsigned int depth;
  230. XGetGeometry( xengine->m_display, win, &junk,
  231. &(m_x), &(m_y),
  232. &(m_width), &(m_height),
  233. &(m_border), &depth );
  234. // We make sure we include borders, since we want decorations.
  235. m_width += m_border * 2;
  236. m_height += m_border * 2;
  237. m_decorations = true;
  238. return;
  239. }
  240. Window root;
  241. Window* children = NULL;
  242. unsigned int childcount;
  243. // Try to get the first child of the specified window, to avoid decorations.
  244. XQueryTree( xengine->m_display, win, &root, &junk, &children, &childcount );
  245. if ( childcount == 1 && children ) {
  246. win = children[ 0 ];
  247. m_decorations = false;
  248. } else {
  249. //fprintf( stderr, "Warning: slop couldn't determine how to remove decorations, continuing without removing decorations...\n" );
  250. m_decorations = true;
  251. }
  252. XWindowAttributes attr;
  253. // We use XGetWindowAttributes to know our root window.
  254. XGetWindowAttributes( xengine->m_display, win, &attr );
  255. //m_x = attr.x;
  256. //m_y = attr.y;
  257. m_width = attr.width;
  258. m_height = attr.height;
  259. m_border = attr.border_width;
  260. XTranslateCoordinates( xengine->m_display, win, attr.root, -attr.border_width, -attr.border_width, &(m_x), &(m_y), &junk );
  261. }
  262. slop::Rectangle::~Rectangle() {
  263. //XFreeGC( xengine->m_display, m_gc );
  264. if ( m_window == None ) {
  265. return;
  266. }
  267. //XFreeColors( xengine->m_display, xengine->m_colormap, m_color.pixel, 1,
  268. // Attempt to move window offscreen before trying to remove it.
  269. //XResizeWindow( xengine->m_display, m_window, 1, 1 );
  270. //XMoveWindow( xengine->m_display, m_window, 0, 0 );
  271. //XUnmapWindow( xengine->m_display, m_window );
  272. XDestroyWindow( xengine->m_display, m_window );
  273. XEvent event;
  274. // Block until the window is actually completely removed.
  275. XIfEvent( xengine->m_display, &event, &isDestroyNotify, (XPointer)&m_window );
  276. }
  277. slop::Rectangle::Rectangle( int x, int y, int width, int height, int border, int padding, float r, float g, float b ) {
  278. m_xoffset = 0;
  279. m_yoffset = 0;
  280. m_x = x;
  281. m_y = y;
  282. m_width = width;
  283. m_height = height;
  284. m_border = border;
  285. m_padding = padding;
  286. m_window = None;
  287. // Convert the width, height, x, and y to coordinates that don't have negative values.
  288. // (also adjust for padding and border size.)
  289. constrain( width, height );
  290. // If we don't have a border, we don't exist, so just die.
  291. if ( m_border == 0 ) {
  292. return;
  293. }
  294. // This sets up m_color
  295. int err = convertColor( r, g, b );
  296. if ( err ) {
  297. fprintf( stderr, "Couldn't allocate color of value %f,%f,%f!\n", r, g, b );
  298. }
  299. XSetWindowAttributes attributes;
  300. // Set up the window so it's our color
  301. attributes.background_pixmap = None;
  302. attributes.background_pixel = m_color.pixel;
  303. // Not actually sure what this does, but it keeps the window from bugging out :u.
  304. attributes.override_redirect = True;
  305. // We must use our color map, because that's where our color is allocated.
  306. attributes.colormap = xengine->m_colormap;
  307. // Make sure we know when we've been successfully destroyed later!
  308. attributes.event_mask = StructureNotifyMask;
  309. unsigned long valueMask = CWBackPixmap | CWBackPixel | CWOverrideRedirect | CWColormap | CWEventMask;
  310. // Create the window offset by our generated offsets (see constrain( float, float ))
  311. m_window = XCreateWindow( xengine->m_display, xengine->m_root, m_x+m_xoffset-m_border, m_y+m_yoffset-m_border, m_width+m_border*2, m_height+m_border*2,
  312. 0, CopyFromParent, InputOutput,
  313. CopyFromParent, valueMask, &attributes );
  314. // Now punch a hole into it so it looks like a selection rectangle!
  315. XRectangle rect;
  316. rect.x = rect.y = m_border;
  317. rect.width = m_width;
  318. rect.height = m_height;
  319. XClassHint classhints;
  320. char name[] = "slop";
  321. classhints.res_name = name;
  322. classhints.res_class = name;
  323. XSetClassHint( xengine->m_display, m_window, &classhints );
  324. XShapeCombineRectangles( xengine->m_display, m_window, ShapeBounding, 0, 0, &rect, 1, ShapeSubtract, 0);
  325. XMapWindow( xengine->m_display, m_window );
  326. }
  327. void slop::Rectangle::setPos( int x, int y ) {
  328. if ( m_x == x && m_y == y ) {
  329. return;
  330. }
  331. m_x = x;
  332. m_y = y;
  333. // If we don't have a border, we don't exist, so just die.
  334. if ( m_border == 0 ) {
  335. return;
  336. }
  337. XMoveWindow( xengine->m_display, m_window, m_x+m_xoffset-m_border, m_y+m_yoffset-m_border );
  338. }
  339. void slop::Rectangle::setDim( int w, int h ) {
  340. if ( m_width == w && m_height == h ) {
  341. return;
  342. }
  343. constrain( w, h );
  344. // If we don't have a border, we don't exist, so just die.
  345. if ( m_border == 0 ) {
  346. return;
  347. }
  348. // Change the window size and location to our generated offsets (see constrain( float, float ))
  349. XResizeWindow( xengine->m_display, m_window, m_width+m_border*2, m_height+m_border*2 );
  350. XMoveWindow( xengine->m_display, m_window, m_x+m_xoffset-m_border, m_y+m_yoffset-m_border );
  351. // Regenerate our hole
  352. XRectangle rect;
  353. rect.x = rect.y = 0;
  354. rect.width = m_width+m_border*2;
  355. rect.height = m_height+m_border*2;
  356. XShapeCombineRectangles( xengine->m_display, m_window, ShapeBounding, 0, 0, &rect, 1, ShapeSet, 0);
  357. // Then punch out another.
  358. rect.x = rect.y = m_border;
  359. rect.width = m_width;
  360. rect.height = m_height;
  361. XShapeCombineRectangles( xengine->m_display, m_window, ShapeBounding, 0, 0, &rect, 1, ShapeSubtract, 0);
  362. }
  363. void slop::XEngine::updateHoverWindow() {
  364. Window root, hoverwin;
  365. int mx, my;
  366. int wx, wy;
  367. unsigned int mask;
  368. // Query the pointer for the child window, the child window is basically the window we're hovering over.
  369. XQueryPointer( m_display, m_root, &root, &hoverwin, &mx, &my, &wx, &wy, &mask );
  370. // If we already know that we're hovering over it, do nothing.
  371. if ( m_hoverWindow == hoverwin ) {
  372. return;
  373. }
  374. // Make sure we can't select one of our selection rectangles, that's just weird.
  375. for ( unsigned int i=0; i<m_rects.size(); i++ ) {
  376. if ( m_rects.at( i )->m_window == hoverwin ) {
  377. return;
  378. }
  379. }
  380. m_hoverWindow = hoverwin;
  381. }
  382. void slop::XEngine::updateHoverWindow( Window hoverwin ) {
  383. // Same thing as updateHoverWindow but it uses the specified child.
  384. // It's used when we first grab the cursor so it's slightly more effecient
  385. // than calling XQueryPointer twice.
  386. if ( m_hoverWindow == hoverwin ) {
  387. return;
  388. }
  389. for ( unsigned int i=0; i<m_rects.size(); i++ ) {
  390. if ( m_rects.at( i )->m_window == hoverwin ) {
  391. return;
  392. }
  393. }
  394. m_hoverWindow = hoverwin;
  395. }
  396. // Keeps our rectangle's sizes all positive, so Xlib doesn't throw an exception.
  397. // It also keeps our values in absolute coordinates which is nice.
  398. void slop::Rectangle::constrain( int w, int h ) {
  399. int pad = m_padding;
  400. if ( pad < 0 && std::abs( w ) < std::abs( pad )*2 ) {
  401. pad = 0;
  402. }
  403. if ( w < 0 ) {
  404. m_flippedx = true;
  405. m_xoffset = w - pad;
  406. m_width = -w + pad*2;
  407. } else {
  408. m_flippedx = false;
  409. m_xoffset = -pad;
  410. m_width = w + pad*2;
  411. }
  412. pad = m_padding;
  413. if ( pad < 0 && std::abs( h ) < std::abs( pad )*2 ) {
  414. pad = 0;
  415. }
  416. if ( h < 0 ) {
  417. m_flippedy = true;
  418. m_yoffset = h - pad;
  419. m_height = -h + pad*2;
  420. } else {
  421. m_flippedy = false;
  422. m_yoffset = -pad;
  423. m_height = h + pad*2;
  424. }
  425. }
  426. int slop::Rectangle::convertColor( float r, float g, float b ) {
  427. // Convert float colors to shorts.
  428. short red = short( floor( r * 65535.f ) );
  429. short green = short( floor( g * 65535.f ) );
  430. short blue = short( floor( b * 65535.f ) );
  431. XColor color;
  432. color.red = red;
  433. color.green = green;
  434. color.blue = blue;
  435. // I don't deallocate this anywhere, I think X handles it ???
  436. int err = XAllocColor( xengine->m_display, xengine->m_colormap, &color );
  437. if ( err == BadColor ) {
  438. return err;
  439. }
  440. m_color = color;
  441. return 0;
  442. }