// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-License-Identifier: BSD-3-Clause // Must be included first to avoid conflicts with X11's `Status` define. #include #include "vtkXRenderWindowInteractor.h" #include #include #include #include #include #include #include "vtkActor.h" #include "vtkCallbackCommand.h" #include "vtkCommand.h" #include "vtkInteractorStyle.h" #include "vtkObjectFactory.h" #include "vtkRenderWindow.h" #include "vtkStringArray.h" #include "vtkX11Functions.h" #include #include #include #include #include #include #include VTK_ABI_NAMESPACE_BEGIN vtkStandardNewMacro(vtkXRenderWindowInteractor); template int XEventTypeEquals(Display*, XEvent* event, XPointer) { return event->type == EventType; } struct vtkXRenderWindowInteractorTimer { unsigned long duration; timeval lastFire; }; constexpr unsigned char XDND_VERSION = 5; // Map between the X native id to our own integer count id. Note this // is separate from the TimerMap in the vtkRenderWindowInteractor // superclass. This is used to avoid passing 64-bit values back // through the "int" return type of InternalCreateTimer. class vtkXRenderWindowInteractorInternals { public: vtkXRenderWindowInteractorInternals() { this->DisplayConnection = -1; this->TimerIdCount = 1; vtkX11FunctionsInitialize(); } ~vtkXRenderWindowInteractorInternals() { vtkX11FunctionsFinalize(); } // duration is in milliseconds int CreateLocalTimer(unsigned long duration) { int id = this->TimerIdCount++; this->LocalToTimer[id].duration = duration; gettimeofday(&this->LocalToTimer[id].lastFire, nullptr); return id; } void DestroyLocalTimer(int id) { this->LocalToTimer.erase(id); } /** * This interactor uses `poll()` to coordinate timers. * Returns true if `poll()` needs to block until some time elapses or a user interaction event * occurs. Returns false if `poll()` needs to block indefinitely until a user interaction event * occurs. The `timeout` arg is populated with a time interval (in milliseconds) that can be * used by `poll()` when it needs to use a timeout. */ bool GetTimeToNextTimer(int& timeout) { bool useTimeout = false; // whether `poll()` must block for some time. uint64_t delta = 0; // in microseconds if (!this->LocalToTimer.empty()) { timeval ctv; gettimeofday(&ctv, nullptr); delta = VTK_UNSIGNED_INT_MAX; // arbitrary high value for time interval for (auto& timer : this->LocalToTimer) { uint64_t duration = timer.second.duration * 1000; // in microsecs uint64_t elapsed = (ctv.tv_sec - timer.second.lastFire.tv_sec) * 1000000 + ctv.tv_usec - timer.second.lastFire.tv_usec; // in microsecs // 0 lets the timer fire immediately. delta = duration > elapsed ? std::min(duration - elapsed, delta) : 0; useTimeout = true; } } // max timeout will be VTK_UNSIGNED_INT_MAX/1000, or 4.3e6 milliseconds timeout = static_cast(delta / 1000); return useTimeout; } void FireTimers(vtkXRenderWindowInteractor* rwi) { if (!this->LocalToTimer.empty()) { timeval ctv; gettimeofday(&ctv, nullptr); std::vector timers( this->LocalToTimer.begin(), this->LocalToTimer.end()); for (auto& timer : timers) { int64_t delta = (ctv.tv_sec - timer.second.lastFire.tv_sec) * 1000000 + ctv.tv_usec - timer.second.lastFire.tv_usec; if (delta / 1000 >= static_cast(timer.second.duration)) { int timerId = rwi->GetVTKTimerId(timer.first); if (timerId != 0) { rwi->InvokeEvent(vtkCommand::TimerEvent, &timerId); if (rwi->IsOneShotTimer(timerId)) { this->DestroyLocalTimer(timer.first); } else { auto it = this->LocalToTimer.find(timer.first); if (it != this->LocalToTimer.end()) { it->second.lastFire.tv_sec = ctv.tv_sec; it->second.lastFire.tv_usec = ctv.tv_usec; } } } } } } } static std::set Instances; // whether application was terminated bool LoopDone = false; int DisplayConnection; private: int TimerIdCount; typedef std::map LocalToTimerType; LocalToTimerType LocalToTimer; }; std::set vtkXRenderWindowInteractorInternals::Instances; // for some reason the X11 def of KeySym is getting messed up typedef XID vtkKeySym; //------------------------------------------------------------------------------ vtkXRenderWindowInteractor::vtkXRenderWindowInteractor() { this->Internal = new vtkXRenderWindowInteractorInternals; this->DisplayId = nullptr; this->WindowId = 0; this->KillAtom = 0; this->XdndSource = 0; this->XdndFormatAtom = 0; this->XdndURIListAtom = 0; this->XdndTypeListAtom = 0; this->XdndEnterAtom = 0; this->XdndPositionAtom = 0; this->XdndDropAtom = 0; this->XdndActionCopyAtom = 0; this->XdndStatusAtom = 0; this->XdndFinishedAtom = 0; } //------------------------------------------------------------------------------ vtkXRenderWindowInteractor::~vtkXRenderWindowInteractor() { this->Finalize(); this->Disable(); delete this->Internal; } //------------------------------------------------------------------------------ // TerminateApp() notifies the event loop to exit. // The event loop is started by Start() or by one own's method. // This results in Start() returning to its caller. void vtkXRenderWindowInteractor::TerminateApp() { if (this->Done) { return; } this->Done = true; if (this->RenderWindow == nullptr || this->RenderWindow->GetGenericDisplayId() == nullptr) { return; } // Send a VTK_BreakXtLoop ClientMessage event to be sure we pop out of the // event loop. This "wakes up" the event loop. Otherwise, it might sit idle // waiting for an event before realizing an exit was requested. XClientMessageEvent client; memset(&client, 0, sizeof(client)); client.type = ClientMessage; // client.serial; //leave zeroed // client.send_event; //leave zeroed client.display = this->DisplayId; client.window = this->WindowId; client.message_type = vtkXInternAtom(this->DisplayId, "VTK_BreakXtLoop", False); client.format = 32; // indicates size of data chunks: 8, 16 or 32 bits... // client.data; //leave zeroed vtkXSendEvent( client.display, client.window, True, NoEventMask, reinterpret_cast(&client)); vtkXFlush(client.display); } void vtkXRenderWindowInteractor::ProcessEvents() { auto& internals = (*this->Internal); auto& done = internals.LoopDone; std::map windowmap; std::set dpys; // Make a copy of Instances, the original set might change during loop std::vector instances( vtkXRenderWindowInteractorInternals::Instances.begin(), vtkXRenderWindowInteractorInternals::Instances.end()); for (auto rwi : instances) { if (rwi->RenderWindow->GetGenericDisplayId() == nullptr) { // The window has closed the display connection rwi->Finalize(); continue; } windowmap.insert({ rwi->WindowId, rwi }); dpys.insert(rwi->DisplayId); } for (Display* dpy : dpys) { XEvent event; while (this->CheckDisplayId(dpy) && vtkXPending(dpy) != 0) { // If events are pending, dispatch them to the right RenderWindowInteractor vtkXNextEvent(dpy, &event); Window w = event.xany.window; auto iter = windowmap.find(w); if (iter != windowmap.end() && !iter->second->Done) { iter->second->DispatchEvent(&event); iter->second->FireTimers(); } } } // Set event loop to terminate if there were no displays to check for events done = dpys.empty(); for (auto rwi : vtkXRenderWindowInteractorInternals::Instances) { if (!rwi->Done) { rwi->FireTimers(); } // Set event loop to terminate if SetDone(true) or TerminateApp() was // called on any of the interactors done = done || rwi->Done; } } //------------------------------------------------------------------------------ void vtkXRenderWindowInteractor::WaitForEvents() { bool useTimeout = false; int soonestTimer = 0; // check to see how long we wait for the next timer for (auto rwi : vtkXRenderWindowInteractorInternals::Instances) { if (rwi->Done) { continue; } int t; bool haveTimer = rwi->Internal->GetTimeToNextTimer(t); if (haveTimer) { if (!useTimeout) { useTimeout = true; soonestTimer = t; } else { soonestTimer = std::min(soonestTimer, t); } } } // build the list of unique display connection fds to poll std::vector in_fds; for (auto rwi : vtkXRenderWindowInteractorInternals::Instances) { if (!rwi->Done && rwi->RenderWindow->GetGenericDisplayId() != nullptr) { int rwi_fd = rwi->Internal->DisplayConnection; auto iter = std::lower_bound(in_fds.begin(), in_fds.end(), rwi_fd, [](const pollfd& pfd, const int& fd) { return pfd.fd < fd; }); if (iter == in_fds.end() || iter->fd != rwi_fd) { in_fds.insert(iter, { rwi_fd, POLLIN, 0 }); } } } if (!in_fds.empty()) { // poll() will wait until timeout elapses or something else wakes us int timeout = useTimeout ? soonestTimer : -1; vtkDebugMacro(<< "X event wait, timeout=" << timeout << "ms"); poll(in_fds.data(), static_cast(in_fds.size()), timeout); } } //------------------------------------------------------------------------------ // This will start up the X event loop. If you // call this method it will loop processing X events until the // loop is exited. void vtkXRenderWindowInteractor::StartEventLoop() { // cannot process events without an X display or window. if (!this->DisplayId || !this->WindowId) { return; } for (auto rwi : vtkXRenderWindowInteractorInternals::Instances) { rwi->Done = false; } auto& loopDone = this->Internal->LoopDone; do { // process pending events. this->ProcessEvents(); // wait for events if application is not yet terminated. if (!loopDone) { this->WaitForEvents(); } } while (!loopDone); } //------------------------------------------------------------------------------ // Initializes the event handlers without an XtAppContext. This is // good for when you don't have a user interface, but you still // want to have mouse interaction. void vtkXRenderWindowInteractor::Initialize() { if (this->Initialized) { return; } vtkRenderWindow* ren; int* size; // make sure we have a RenderWindow and camera if (!this->RenderWindow) { vtkErrorMacro(<< "No renderer defined!"); return; } this->Initialized = 1; ren = this->RenderWindow; ren->EnsureDisplay(); this->DisplayId = static_cast(ren->GetGenericDisplayId()); vtkXRenderWindowInteractorInternals::Instances.insert(this); size = ren->GetActualSize(); size[0] = ((size[0] > 0) ? size[0] : 300); size[1] = ((size[1] > 0) ? size[1] : 300); if (this->DisplayId) { this->Internal->DisplayConnection = ConnectionNumber(this->DisplayId); vtkXSync(this->DisplayId, False); } ren->Start(); ren->End(); this->WindowId = reinterpret_cast(ren->GetGenericWindowId()); if (this->DisplayId && this->WindowId) { XWindowAttributes attribs; // Find the current window size auto* previousHandler = vtkXSetErrorHandler([](Display*, XErrorEvent*) { return 0; }); if (vtkXGetWindowAttributes(this->DisplayId, this->WindowId, &attribs)) { size[0] = attribs.width; size[1] = attribs.height; } else { // window does not exist. `ren` is not an X11 render window. this->WindowId = 0; } vtkXSetErrorHandler(previousHandler); } ren->SetSize(size[0], size[1]); this->Enable(); this->Size[0] = size[0]; this->Size[1] = size[1]; } //------------------------------------------------------------------------------ bool vtkXRenderWindowInteractor::CheckDisplayId(Display* dpy) { bool good = false; for (auto rwi : vtkXRenderWindowInteractorInternals::Instances) { if (rwi->DisplayId == dpy) { if (rwi->RenderWindow->GetGenericDisplayId() != nullptr) { good = true; continue; } vtkDebugMacro(<< "RenderWindow->DisplayId is null for " << rwi->GetObjectDescription()); } } return good; } //------------------------------------------------------------------------------ void vtkXRenderWindowInteractor::Finalize() { vtkXRenderWindowInteractorInternals::Instances.erase(this); if (this->RenderWindow) { // Finalize the window this->RenderWindow->Finalize(); } // disconnect from the display, even if we didn't own it this->DisplayId = nullptr; this->Internal->DisplayConnection = -1; // revert to uninitialized state this->Initialized = false; this->Enabled = false; } //------------------------------------------------------------------------------ void vtkXRenderWindowInteractor::Enable() { // avoid cycles of calling Initialize() and Enable() if (this->Enabled) { return; } // When we're attached to an offscreen render window, // there is no real X Display or X Window. if (!this->WindowId || !this->DisplayId) { this->Enabled = 1; this->Modified(); return; } // Add the event handler to the system. // If we change the types of events processed by this handler, then // we need to change the Disable() routine to match. In order for Disable() // to work properly, both the callback function AND the client data // passed to XtAddEventHandler and XtRemoveEventHandler must MATCH // PERFECTLY vtkXSelectInput(this->DisplayId, this->WindowId, KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | ExposureMask | StructureNotifyMask | EnterWindowMask | LeaveWindowMask | PointerMotionHintMask | PointerMotionMask); // Setup for capturing the window deletion this->KillAtom = vtkXInternAtom(this->DisplayId, "WM_DELETE_WINDOW", False); vtkXSetWMProtocols(this->DisplayId, this->WindowId, &this->KillAtom, 1); // Enable drag and drop Atom xdndAwareAtom = vtkXInternAtom(this->DisplayId, "XdndAware", False); vtkXChangeProperty( this->DisplayId, this->WindowId, xdndAwareAtom, XA_ATOM, 32, PropModeReplace, &XDND_VERSION, 1); this->XdndURIListAtom = vtkXInternAtom(this->DisplayId, "text/uri-list", False); this->XdndTypeListAtom = vtkXInternAtom(this->DisplayId, "XdndTypeList", False); this->XdndEnterAtom = vtkXInternAtom(this->DisplayId, "XdndEnter", False); this->XdndPositionAtom = vtkXInternAtom(this->DisplayId, "XdndPosition", False); this->XdndDropAtom = vtkXInternAtom(this->DisplayId, "XdndDrop", False); this->XdndActionCopyAtom = vtkXInternAtom(this->DisplayId, "XdndActionCopy", False); this->XdndStatusAtom = vtkXInternAtom(this->DisplayId, "XdndStatus", False); this->XdndFinishedAtom = vtkXInternAtom(this->DisplayId, "XdndFinished", False); this->Enabled = 1; this->Modified(); } //------------------------------------------------------------------------------ void vtkXRenderWindowInteractor::Disable() { if (!this->Enabled) { return; } this->Enabled = 0; this->Modified(); } //------------------------------------------------------------------------------ void vtkXRenderWindowInteractor::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); } //------------------------------------------------------------------------------ void vtkXRenderWindowInteractor::UpdateSize(int x, int y) { // if the size changed send this on to the RenderWindow if ((x != this->Size[0]) || (y != this->Size[1])) { this->Size[0] = x; this->Size[1] = y; this->RenderWindow->SetSize(x, y); } } //------------------------------------------------------------------------------ void vtkXRenderWindowInteractor::UpdateSizeNoXResize(int x, int y) { // if the size changed send this on to the RenderWindow if ((x != this->Size[0]) || (y != this->Size[1])) { this->Size[0] = x; this->Size[1] = y; // change the ivars but don't resize the X window this->RenderWindow->vtkRenderWindow::SetSize(x, y); } } //------------------------------------------------------------------------------ void vtkXRenderWindowInteractor::FireTimers() { if (this->GetEnabled()) { this->Internal->FireTimers(this); } } //------------------------------------------------------------------------------ // X always creates one shot timers int vtkXRenderWindowInteractor::InternalCreateTimer( int vtkNotUsed(timerId), int vtkNotUsed(timerType), unsigned long duration) { duration = (duration > 0 ? duration : this->TimerDuration); int platformTimerId = this->Internal->CreateLocalTimer(duration); return platformTimerId; } //------------------------------------------------------------------------------ int vtkXRenderWindowInteractor::InternalDestroyTimer(int platformTimerId) { this->Internal->DestroyLocalTimer(platformTimerId); return 1; } //------------------------------------------------------------------------------ void vtkXRenderWindowInteractor::DispatchEvent(XEvent* event) { int xp, yp; switch (event->type) { case Expose: { if (!this->Enabled) { return; } XEvent result; while (vtkXCheckTypedWindowEvent(this->DisplayId, this->WindowId, Expose, &result)) { // just getting the expose configure event event = &result; } XExposeEvent* exposeEvent = reinterpret_cast(event); this->SetEventSize(exposeEvent->width, exposeEvent->height); xp = exposeEvent->x; yp = exposeEvent->y; yp = this->Size[1] - yp - 1; this->SetEventPosition(xp, yp); // only render if we are currently accepting events if (this->Enabled) { this->InvokeEvent(vtkCommand::ExposeEvent, nullptr); this->Render(); } } break; case MapNotify: { // only render if we are currently accepting events if (this->Enabled && this->GetRenderWindow()->GetNeverRendered()) { this->Render(); } } break; case ConfigureNotify: { XEvent result; while (vtkXCheckTypedWindowEvent(this->DisplayId, this->WindowId, ConfigureNotify, &result)) { // just getting the last configure event event = &result; } XConfigureEvent* configureEvent = reinterpret_cast(event); int width = configureEvent->width; int height = configureEvent->height; if (width != this->Size[0] || height != this->Size[1]) { bool resizeSmaller = width <= this->Size[0] && height <= this->Size[1]; this->UpdateSizeNoXResize(width, height); xp = configureEvent->x; yp = configureEvent->y; this->SetEventPosition(xp, this->Size[1] - yp - 1); // only render if we are currently accepting events if (this->Enabled) { this->InvokeEvent(vtkCommand::ConfigureEvent, nullptr); if (resizeSmaller) { // Don't call Render when the window is resized to be larger: // // - if the window is resized to be larger, an Expose event will // be triggered by the X server which will trigger a call to // Render(). // - if the window is resized to be smaller, no Expose event will // be triggered by the X server, as no new area become visible. // only in this case, we need to explicitly call Render() // in ConfigureNotify. this->Render(); } } } } break; case ButtonPress: { if (!this->Enabled) { return; } XButtonEvent* buttonEvent = reinterpret_cast(event); int ctrl = (buttonEvent->state & ControlMask) ? 1 : 0; int shift = (buttonEvent->state & ShiftMask) ? 1 : 0; int alt = (buttonEvent->state & Mod1Mask) ? 1 : 0; xp = buttonEvent->x; yp = buttonEvent->y; // check for double click static int MousePressTime = 0; int repeat = 0; // 400 ms threshold by default is probably good to start int eventTime = static_cast(buttonEvent->time); if ((eventTime - MousePressTime) < 400) { MousePressTime -= 2000; // no double click next time repeat = 1; } else { MousePressTime = eventTime; } this->SetEventInformationFlipY(xp, yp, ctrl, shift, 0, repeat); this->SetAltKey(alt); switch (buttonEvent->button) { case Button1: this->InvokeEvent(vtkCommand::LeftButtonPressEvent, nullptr); break; case Button2: this->InvokeEvent(vtkCommand::MiddleButtonPressEvent, nullptr); break; case Button3: this->InvokeEvent(vtkCommand::RightButtonPressEvent, nullptr); break; case Button4: this->InvokeEvent(vtkCommand::MouseWheelForwardEvent, nullptr); break; case Button5: this->InvokeEvent(vtkCommand::MouseWheelBackwardEvent, nullptr); break; } } break; case ButtonRelease: { if (!this->Enabled) { return; } XButtonEvent* buttonEvent = reinterpret_cast(event); int ctrl = (buttonEvent->state & ControlMask) ? 1 : 0; int shift = (buttonEvent->state & ShiftMask) ? 1 : 0; int alt = (buttonEvent->state & Mod1Mask) ? 1 : 0; xp = buttonEvent->x; yp = buttonEvent->y; this->SetEventInformationFlipY(xp, yp, ctrl, shift); this->SetAltKey(alt); switch (buttonEvent->button) { case Button1: this->InvokeEvent(vtkCommand::LeftButtonReleaseEvent, nullptr); break; case Button2: this->InvokeEvent(vtkCommand::MiddleButtonReleaseEvent, nullptr); break; case Button3: this->InvokeEvent(vtkCommand::RightButtonReleaseEvent, nullptr); break; } } break; case EnterNotify: { // Force the keyboard focus to be this render window vtkXSetInputFocus(this->DisplayId, this->WindowId, RevertToPointerRoot, CurrentTime); if (this->Enabled) { XEnterWindowEvent* e = reinterpret_cast(event); this->SetEventInformationFlipY( e->x, e->y, (e->state & ControlMask) != 0, (e->state & ShiftMask) != 0); this->SetAltKey((e->state & Mod1Mask) ? 1 : 0); this->InvokeEvent(vtkCommand::EnterEvent, nullptr); } } break; case LeaveNotify: { if (this->Enabled) { XLeaveWindowEvent* e = reinterpret_cast(event); this->SetEventInformationFlipY( e->x, e->y, (e->state & ControlMask) != 0, (e->state & ShiftMask) != 0); this->SetAltKey((e->state & Mod1Mask) ? 1 : 0); this->InvokeEvent(vtkCommand::LeaveEvent, nullptr); } } break; case KeyPress: { if (!this->Enabled) { return; } XKeyEvent* keyEvent = reinterpret_cast(event); int ctrl = (keyEvent->state & ControlMask) ? 1 : 0; int shift = (keyEvent->state & ShiftMask) ? 1 : 0; int alt = (keyEvent->state & Mod1Mask) ? 1 : 0; // XLookupString provide a keycode as a char in Basic Latin and Latin1 // unicode blocks. We care only for the first char of the keycode. char keyCode; vtkKeySym keySym; vtkXLookupString(keyEvent, &keyCode, 1, &keySym, nullptr); xp = keyEvent->x; yp = keyEvent->y; this->SetEventInformationFlipY(xp, yp, ctrl, shift, keyCode, 1, vtkXKeysymToString(keySym)); this->SetAltKey(alt); this->InvokeEvent(vtkCommand::KeyPressEvent, nullptr); this->InvokeEvent(vtkCommand::CharEvent, nullptr); } break; case KeyRelease: { if (!this->Enabled) { return; } XKeyEvent* keyEvent = reinterpret_cast(event); int ctrl = (keyEvent->state & ControlMask) ? 1 : 0; int shift = (keyEvent->state & ShiftMask) ? 1 : 0; int alt = (keyEvent->state & Mod1Mask) ? 1 : 0; // XLookupString provide a keycode as a char in Basic Latin and Latin1 // unicode blocks. We care only for the first char of the keycode. char keyCode; vtkKeySym keySym; vtkXLookupString(keyEvent, &keyCode, 1, &keySym, nullptr); xp = keyEvent->x; yp = keyEvent->y; this->SetEventInformationFlipY(xp, yp, ctrl, shift, keyCode, 1, vtkXKeysymToString(keySym)); this->SetAltKey(alt); this->InvokeEvent(vtkCommand::KeyReleaseEvent, nullptr); } break; case MotionNotify: { if (!this->Enabled) { return; } XMotionEvent* motionEvent = reinterpret_cast(event); int ctrl = (motionEvent->state & ControlMask) ? 1 : 0; int shift = (motionEvent->state & ShiftMask) ? 1 : 0; int alt = (motionEvent->state & Mod1Mask) ? 1 : 0; // Note that even though the (x,y) location of the pointer is event structure, // we must call XQueryPointer for the hints (motion event compression) to // work properly. this->GetMousePosition(&xp, &yp); this->SetEventInformation(xp, yp, ctrl, shift); this->SetAltKey(alt); this->InvokeEvent(vtkCommand::MouseMoveEvent, nullptr); } break; // Selection request for drag and drop has been delivered case SelectionNotify: { // Sanity checks if (!event->xselection.property || !this->XdndSource) { return; } // Recover the dropped file char* data = nullptr; Atom actualType; int actualFormat; unsigned long itemCount, bytesAfter; vtkXGetWindowProperty(this->DisplayId, event->xselection.requestor, event->xselection.property, 0, LONG_MAX, False, event->xselection.target, &actualType, &actualFormat, &itemCount, &bytesAfter, reinterpret_cast(&data)); // Conversion checks if ((event->xselection.target != AnyPropertyType && actualType != event->xselection.target) || itemCount == 0) { return; } // Recover filepaths from uris and invoke DropFilesEvent std::stringstream uris(data); std::string uri, protocol, hostname, filePath; std::string unused0, unused1, unused3; vtkNew filePaths; while (std::getline(uris, uri, '\n')) { if (vtksys::SystemTools::ParseURL( uri, protocol, unused0, unused1, hostname, unused3, filePath, true)) { if (protocol == "file" && (hostname.empty() || hostname == "localhost")) { // The uris can be crlf delimited, remove ending \r if any if (filePath.back() == '\r') { filePath.pop_back(); } // The extracted filepath miss the first slash filePath.insert(0, "/"); filePaths->InsertNextValue(filePath); } } } this->InvokeEvent(vtkCommand::DropFilesEvent, filePaths); vtkXFree(data); // Inform the source the the drag and drop operation was successful XEvent reply; memset(&reply, 0, sizeof(reply)); reply.type = ClientMessage; reply.xclient.window = this->XdndSource; reply.xclient.message_type = this->XdndFinishedAtom; reply.xclient.format = 32; reply.xclient.data.l[0] = this->WindowId; reply.xclient.data.l[1] = 1; if (this->XdndSourceVersion >= 2) { reply.xclient.data.l[2] = this->XdndActionCopyAtom; } vtkXSendEvent(this->DisplayId, this->XdndSource, False, NoEventMask, &reply); vtkXFlush(this->DisplayId); this->XdndSource = 0; } break; case ClientMessage: { if (event->xclient.message_type == this->XdndEnterAtom) { // Drag and drop event enter the window this->XdndSource = event->xclient.data.l[0]; // Check version this->XdndSourceVersion = event->xclient.data.l[1] >> 24; if (this->XdndSourceVersion > XDND_VERSION) { return; } // Recover the formats provided by the dnd source Atom* formats = nullptr; unsigned long count; bool list = event->xclient.data.l[1] & 1; if (list) { Atom actualType; int actualFormat; unsigned long bytesAfter; vtkXGetWindowProperty(this->DisplayId, this->XdndSource, this->XdndTypeListAtom, 0, LONG_MAX, False, XA_ATOM, &actualType, &actualFormat, &count, &bytesAfter, reinterpret_cast(&formats)); } else { count = 3; formats = reinterpret_cast(event->xclient.data.l + 2); } // Check one of these format is an URI list // Which is the only supported format for (unsigned long i = 0; i < count; i++) { if (formats[i] == this->XdndURIListAtom) { this->XdndFormatAtom = XdndURIListAtom; break; } } // Free the allocated formats if (list && formats) { vtkXFree(formats); } } if (event->xclient.message_type == this->XdndPositionAtom) { // Drag and drop event inside the window if (this->XdndSource != static_cast(event->xclient.data.l[0])) { vtkWarningMacro("Only one dnd action at a time is supported"); return; } // Recover the position int location[2]; unsigned int keys; this->GetMousePositionAndModifierKeysState(&location[0], &location[1], &keys); int ctrl = keys & ControlMask ? 1 : 0; int shift = keys & ShiftMask ? 1 : 0; int alt = keys & Mod1Mask ? 1 : 0; this->SetEventInformationFlipY(location[0], location[1], ctrl, shift); this->SetAltKey(alt); this->InvokeEvent(vtkCommand::UpdateDropLocationEvent, location); // Reply that we are ready to copy the dragged data XEvent reply; memset(&reply, 0, sizeof(reply)); reply.type = ClientMessage; reply.xclient.window = this->XdndSource; reply.xclient.message_type = this->XdndStatusAtom; reply.xclient.format = 32; reply.xclient.data.l[0] = this->WindowId; reply.xclient.data.l[1] = 1; // Always accept the dnd with no rectangle reply.xclient.data.l[2] = 0; // Specify an empty rectangle reply.xclient.data.l[3] = 0; reply.xclient.data.l[4] = this->XdndActionCopyAtom; vtkXSendEvent(this->DisplayId, this->XdndSource, False, NoEventMask, &reply); vtkXFlush(this->DisplayId); } else if (event->xclient.message_type == this->XdndDropAtom) { // Item dropped in the window if (this->XdndSource != static_cast(event->xclient.data.l[0])) { vtkWarningMacro("Only one dnd action at a time is supported"); return; } if (this->XdndFormatAtom) { // Ask for a conversion of the selection. This will trigger a SelectionNotify event later. Atom xdndSelectionAtom = vtkXInternAtom(this->DisplayId, "XdndSelection", False); vtkXConvertSelection(this->DisplayId, xdndSelectionAtom, this->XdndFormatAtom, xdndSelectionAtom, this->WindowId, CurrentTime); } else if (this->XdndSourceVersion >= 2) { XEvent reply; memset(&reply, 0, sizeof(reply)); reply.type = ClientMessage; reply.xclient.window = this->XdndSource; reply.xclient.message_type = this->XdndFinishedAtom; reply.xclient.format = 32; reply.xclient.data.l[0] = this->WindowId; reply.xclient.data.l[1] = 0; // The drag was rejected reply.xclient.data.l[2] = None; vtkXSendEvent(this->DisplayId, this->XdndSource, False, NoEventMask, &reply); vtkXFlush(this->DisplayId); } } else if (static_cast(event->xclient.data.l[0]) == this->KillAtom) { this->ExitCallback(); } } break; } } //------------------------------------------------------------------------------ void vtkXRenderWindowInteractor::GetMousePosition(int* x, int* y) { unsigned int keys; this->GetMousePositionAndModifierKeysState(x, y, &keys); } //------------------------------------------------------------------------------ void vtkXRenderWindowInteractor::GetMousePositionAndModifierKeysState( int* x, int* y, unsigned int* keys) { Window root, child; int root_x, root_y; vtkXQueryPointer(this->DisplayId, this->WindowId, &root, &child, &root_x, &root_y, x, y, keys); *y = this->Size[1] - *y - 1; } //------------------------------------------------------------------------------ // void vtkXRenderWindowInteractor::Timer(XtPointer client_data, XtIntervalId* id) // { // vtkXRenderWindowInteractorTimer(client_data, id); // } //------------------------------------------------------------------------------ // void vtkXRenderWindowInteractor::Callback( // Widget w, XtPointer client_data, XEvent* event, Boolean* ctd) // { // vtkXRenderWindowInteractorCallback(w, client_data, event, ctd); // } VTK_ABI_NAMESPACE_END