Minsky
windowInformation.cc
Go to the documentation of this file.
1 /*
2  @copyright Steve Keen 2021
3  @author Janak Porwal
4  This file is part of Minsky.
5 
6  Minsky is free software: you can redistribute it and/or modify it
7  under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  Minsky is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with Minsky. If not, see <http://www.gnu.org/licenses/>.
18 */
19 #define NTDDI_VERSION NTDDI_WINBLUE
20 #include "windowInformation.h"
21 #include "minsky_epilogue.h"
22 
23 #include <stdexcept>
24 #include <string>
25 #include <mutex>
26 
27 #if defined(CAIRO_HAS_WIN32_SURFACE) && !defined(__CYGWIN__)
28 #define USE_WIN32_SURFACE
29 #elif defined(CAIRO_HAS_XLIB_SURFACE) && !defined(MAC_OSX_TK)
30 #define USE_X11
31 #include <cairo/cairo-xlib.h>
32 #include <X11/Xlib.h>
33 #endif
34 
35 
36 #ifdef _WIN32
37 #undef Realloc
38 #include <windows.h>
39 #include <windowsx.h>
40 #include <wingdi.h>
41 #include <winuser.h>
42 #include <shellscalingapi.h>
43 #ifdef USE_WIN32_SURFACE
44 #include <cairo/cairo-win32.h>
45 #endif
46 #endif
47 
48 #if defined(MAC_OSX_TK)
49 #include <Carbon/Carbon.h>
50 #include <cairo/cairo-quartz.h>
51 #include "getContext.h"
52 #endif
53 
54 using namespace std;
55 using namespace ecolab;
56 
57 namespace minsky
58 {
59  namespace
60  {
61 #ifdef USE_WIN32_SURFACE
62 #elif defined(MAC_OSX_TK)
63 #else
64  // log any X11 errors on the console - X11 errors will occur on a
65  // background thread that may not catch exceptions, so exceptions
66  // are inappropriate.
67  int throwOnXError(Display *, XErrorEvent *ev)
68  {
69  char errorMessage[256];
70  XGetErrorText(ev->display, ev->error_code, errorMessage, sizeof(errorMessage));
71  fputs(errorMessage,stderr);
72  return 0;
73  }
74 
75  int xinitThreadsStatus=XInitThreads();
76 #endif
77 
78  void blit(const Winfo& winfo, int x, int y, int width, int height)
79  {
80 #ifdef USE_WIN32_SURFACE
81  PAINTSTRUCT ps;
82  HDC dc=BeginPaint(winfo.childWindowId, &ps);
83  BitBlt(dc, x, y, width,height,winfo.hdcMem,x,y,SRCCOPY);
84  EndPaint(winfo.childWindowId, &ps);
85  SetWindowPos(winfo.childWindowId,HWND_TOP,winfo.offsetLeft,winfo.offsetTop,winfo.childWidth,winfo.childHeight,0);
86 #elif defined(USE_X11)
87  static mutex blitting;
88  const lock_guard<mutex> lock(blitting);
89  XCopyArea(winfo.display, winfo.bufferWindowId, winfo.childWindowId, winfo.graphicsContext, x, y, width, height, x, y);
90  XFlush(winfo.display);
91  XRaiseWindow(winfo.display, winfo.childWindowId);
92 #endif
93  }
94  }
95 
96  WindowInformation::~WindowInformation()
97  {
98  bufferSurface.reset();
99 #ifdef USE_WIN32_SURFACE
100  SelectObject(hdcMem, hOld);
101  DeleteObject(hbmMem);
102  DeleteDC(hdcMem);
103  SetWindowLongPtrA(childWindowId, GWLP_USERDATA, 0);
104  PostMessageA(childWindowId,WM_CLOSE,0,0);
105 #elif defined(MAC_OSX_TK)
106 #elif defined(USE_X11)
107  XFreeGC(display, graphicsContext);
108  XDestroyWindow(display, childWindowId);
109  XDestroyWindow(display, bufferWindowId);
110 #endif
111  }
112 
113  const ecolab::cairo::SurfacePtr& WindowInformation::getBufferSurface() const
114  {
115  return bufferSurface;
116  }
117 
118  void WindowInformation::copyBufferToMain()
119  {
120  cairo_surface_flush(bufferSurface->surface());
121 #ifdef USE_WIN32_SURFACE
122  InvalidateRect(childWindowId,nullptr,true);
123  PostMessageA(childWindowId,WM_PAINT,0,0);
124 #elif defined(USE_X11)
125  try {
126  blit(*this,0,0,childWidth,childHeight);
127  }
128  catch (...) {}
129  return;
130 
131  XEvent event;
132  XExposeEvent& expose=event.xexpose;
133  expose.type=Expose;
134  expose.x=expose.y=0;
135  expose.width=childWidth;
136  expose.height=childHeight;
137  expose.window=childWindowId;
138  expose.display=display;
139  expose.count=0;
140  expose.serial=0;
141  try
142  {
143  XSendEvent(display,childWindowId,false,ExposureMask,&event);
144  }
145  catch (...) {}
146 #endif
147  }
148 
149 #ifdef USE_WIN32_SURFACE
150  LRESULT CALLBACK windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
151  {
152  switch (msg)
153  {
154  case WM_PAINT:
155  if (WindowInformation* winfo=reinterpret_cast<WindowInformation*>(GetWindowLongPtr(hwnd, GWLP_USERDATA)))
156  {
157  RECT r;
158  if (GetUpdateRect(hwnd,&r,false))
159  blit(*winfo, r.left, r.top, r.right-r.left, r.bottom-r.top);
160  }
161  return 0;
162  case WM_NCHITTEST:
163  return HTTRANSPARENT;
164  case WM_ERASEBKGND:
165  return 0; // do nothing, to prevent ugly black flashes
166  default:
167  return DefWindowProc(hwnd, msg, wparam, lparam);
168  }
169  }
170 #endif
171 
172 #if defined(USE_X11)
173  void WindowInformation::EventThread::run()
174  {
175 #if defined(_PTHREAD_H) && defined(__USE_GNU) && !defined(NDEBUG)
176  pthread_setname_np(pthread_self(),"event handler");
177 #endif
178  this_thread::sleep_for(1000ms); //why? Even though this thread is
179  //created at the end of
180  //WindowInformation, it appears
181  //the object is still in a fragile
182  //state while the unique_ptr is
183  //being constructed.
184  for (;;)
185  try
186  {
187  XEvent event;
188  bool eventReceived;
189  eventReceived=XCheckWindowEvent(display, childWindowId, ExposureMask|StructureNotifyMask, &event);
190  if (!eventReceived)
191  {
192  this_thread::sleep_for(50ms); //thottle, to avoid starving other threads
193  continue;
194  }
195  switch (event.type)
196  {
197  case Expose:
198  blit(*this,event.xexpose.x, event.xexpose.y, event.xexpose.width, event.xexpose.height);
199  break;
200  case DestroyNotify:
201  return; // exit thread, window has gone away
202  }
203  }
204  catch (const std::exception& ex)
205  {
206  // absorb and log, not much else we can do with X11 errors at this point
207  cerr << ex.what() << endl;
208  return;
209  }
210  catch (...) {return;}
211  }
212 #endif
213 
214  WindowInformation::WindowInformation(uint64_t parentWin, int left, int top, int cWidth, int cHeight,
215  double sf,bool hasScrollBars, const std::function<void(void)>& draw):
216 #ifdef MAC_OSX_TK
217  Winfo(NSContext(reinterpret_cast<void*>(parentWin),left,top,cWidth,cHeight,*this)), draw(draw),
218 #endif
219  hasScrollBars(hasScrollBars)
220  {
221 
222  offsetLeft = left;
223  offsetTop = top;
224 
225 #ifdef MAC_OSX_TK
226  childWidth = cWidth;
227  childHeight = cHeight;
228 #else
229  auto scrollBarOffs=hasScrollBars? 20:0;
230  childWidth = cWidth - scrollBarOffs;
231  childHeight = cHeight - scrollBarOffs;
232 #endif
233 
234 #ifdef USE_WIN32_SURFACE
235  parentWindowId = reinterpret_cast<HWND>(parentWin);
236 
237  if (sf<=0)
238  {
239  // adjust everything by the monitor scale factor
240  DEVICE_SCALE_FACTOR scaleFactor;
241  GetScaleFactorForMonitor(MonitorFromWindow(parentWindowId, MONITOR_DEFAULTTONEAREST), &scaleFactor);
242  sf=int(scaleFactor)/100.0;
243  }
244  if (sf>0)
245  {
246  offsetLeft*=sf;
247  offsetTop*=sf;
248  childWidth*=sf;
249  childHeight*=sf;
250  }
251 
252  // compute menu bar offset
253  RECT window, client;
254  GetWindowRect(parentWindowId,&window);
255  GetClientRect(parentWindowId,&client);
256  POINT clientTopLeft{client.left,client.top};
257  ClientToScreen(parentWindowId,&clientTopLeft);
258 
259  auto offs=clientTopLeft.y-window.top;
260  // adjust for borders
261  auto style=GetWindowLong(parentWindowId,GWL_STYLE);
262  if (style & WS_BORDER) offs-=GetSystemMetrics(SM_CYBORDER);
263  if (style & WS_THICKFRAME) offs-=GetSystemMetrics(SM_CYSIZEFRAME);
264  offsetTop += offs;
265 
266  SetWindowLongPtrA(parentWindowId, GWL_STYLE, style|WS_CLIPCHILDREN);
267  childWindowId=CreateWindowA("Button", "", WS_CHILD | WS_VISIBLE|WS_CLIPSIBLINGS, offsetLeft, offsetTop, childWidth, childHeight, parentWindowId, nullptr, nullptr, nullptr);
268  SetWindowRgn(childWindowId,CreateRectRgn(0,0,childWidth, childHeight),true);
269  HDC hdc=GetDC(childWindowId);
270  hdcMem=CreateCompatibleDC(hdc);
271  hbmMem=CreateCompatibleBitmap(hdc, childWidth, childHeight);
272  ReleaseDC(parentWindowId, hdc);
273  hOld=SelectObject(hdcMem, hbmMem);
274  bufferSurface.reset(new cairo::Surface(cairo_win32_surface_create(hdcMem),childWidth, childHeight));
275  if (sf>0)
276  cairo_surface_set_device_scale(bufferSurface->surface(), sf, sf);
277  SetWindowLongPtrA(childWindowId, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
278  SetWindowLongPtrA(childWindowId, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(windowProc));
279 #elif defined(USE_X11)
280  parentWindowId = parentWin;
281  static const bool errorHandlingSet = (XSetErrorHandler(throwOnXError), true);
282  display = XOpenDisplay(nullptr);
283  const int err = XGetWindowAttributes(display, parentWin, &wAttr);
284  if (err > 1)
285  throw runtime_error("Invalid window: " + to_string(parentWin));
286 
287  // TODO:: Do some sanity checks on dimensions
288 
289  childWindowId = XCreateWindow(display, parentWin, offsetLeft, offsetTop, childWidth, childHeight, 0, CopyFromParent, CopyFromParent, CopyFromParent, 0, nullptr);
290  bufferWindowId = XCreatePixmap(display, parentWin, childWidth, childHeight, wAttr.depth);
291  graphicsContext=XCreateGC(display, childWindowId, 0, nullptr);
292 
293  XMapWindow(display, childWindowId);
294  bufferSurface.reset(new cairo::Surface(cairo_xlib_surface_create(display, bufferWindowId, wAttr.visual, childWidth, childHeight), childWidth, childHeight));
295 
296  // listen to expose events
297  XSelectInput(display, childWindowId, ExposureMask|StructureNotifyMask);
298  eventThread.reset(new EventThread(*this)); // delay construction of this until after the window is created
299 
300 #endif
301  }
302 
304  {
305 #ifdef MAC_OSX_TK
306  nsContext.requestRedraw();
307 #endif
308  }
309 
310 } // namespace minsky
ecolab::cairo::SurfacePtr bufferSurface
STL namespace.
Creation and access to the minskyTCL_obj object, which has code to record whenever Minsky&#39;s state cha...
Definition: constMap.h:22
string to_string(CONST84 char *x)
Definition: minskyTCLObj.h:33
void blit(const Winfo &winfo, int x, int y, int width, int height)