241 lines
5.8 KiB
C++
241 lines
5.8 KiB
C++
#include "rotator.h"
|
|
|
|
#include <QDebug>
|
|
#include <QDateTime>
|
|
#include <QSvgRenderer>
|
|
#include <QTimer>
|
|
#include <QtMath>
|
|
#include <QColor>
|
|
#include <QPainter>
|
|
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xutil.h>
|
|
#include <X11/Xcursor/Xcursor.h>
|
|
|
|
const QPointF Rotator::Gravity(0, 50);
|
|
|
|
static QPointF rotate(QPointF point, double angle) {
|
|
const double c = qCos(qDegreesToRadians(angle));
|
|
const double s = qSin(qDegreesToRadians(angle));
|
|
|
|
return {point.x() * c - point.y() * s, point.x() * s + point.y() * c};
|
|
}
|
|
|
|
static double lengthOfPoint(QPointF point) {
|
|
return std::sqrt((point.x() * point.x()) + (point.y() * point.y()));
|
|
}
|
|
|
|
static double clampDegrees(double deg) {
|
|
while(deg < 0) {
|
|
deg += 360.0;
|
|
}
|
|
|
|
while(deg > 360) {
|
|
deg -= 360.0;
|
|
}
|
|
|
|
return deg;
|
|
}
|
|
|
|
static double clampAngleDiff(double diff) {
|
|
diff = fmod(diff + 180.0, 360.0);
|
|
if (diff < 0) {
|
|
diff += 360.0;
|
|
}
|
|
return diff - 180.0;
|
|
}
|
|
|
|
static double pointToAngle(QPointF point) {
|
|
return clampDegrees(-qRadiansToDegrees(qAtan2(point.x(), point.y())));
|
|
}
|
|
|
|
Rotator::Rotator(QObject* parent) : QObject(parent), disp(XOpenDisplay(nullptr)), renderer(new QSvgRenderer(QString("dmz3.svg"), this)), loopTimer(new QTimer(nullptr)),
|
|
lastTimePoint(QDateTime::currentMSecsSinceEpoch()), lastPosition(-1, -1), angle(0), angleVelocity(0) {
|
|
QObject::connect(loopTimer, &QTimer::timeout, this, &Rotator::loop);
|
|
loopTimer->setInterval(std::chrono::milliseconds(10));
|
|
loopTimer->start();
|
|
|
|
// test
|
|
/*
|
|
for(int i = -360; i < 360; ++i) {
|
|
qDebug() << i << clampAngleDiff(i);
|
|
}
|
|
throw std::exception();
|
|
*/
|
|
}
|
|
|
|
Rotator::~Rotator() {
|
|
if(disp) {
|
|
XCloseDisplay(disp);
|
|
}
|
|
}
|
|
|
|
void Rotator::forAllWindows(std::function<void(Window)> func) {
|
|
forAllWindows(func, DefaultRootWindow(disp));
|
|
}
|
|
|
|
void Rotator::reset() {
|
|
angle = 0;
|
|
angleVelocity = 0;
|
|
}
|
|
|
|
void Rotator::loop() {
|
|
int x = 0, y = 0;
|
|
unsigned int mask = 0;
|
|
Window rootRet;
|
|
Window childRet;
|
|
int xChild = 0, yChild = 0;
|
|
if(XQueryPointer(disp, DefaultRootWindow(disp), &rootRet, &childRet, &x, &y, &xChild, &yChild, &mask)) {
|
|
recalculateCursor({(double) x, (double) y});
|
|
}
|
|
}
|
|
|
|
void Rotator::recalculateCursor(QPointF newPos) {
|
|
quint64 currentTimePoint = QDateTime::currentMSecsSinceEpoch();
|
|
|
|
if(lastPosition.x() == -1) {
|
|
lastPosition = newPos;
|
|
lastTimePoint = currentTimePoint;
|
|
return;
|
|
}
|
|
|
|
const double timeDiff = (currentTimePoint - lastTimePoint) / 1000.0;
|
|
|
|
QPointF mouseForceVector = lastPosition - newPos;
|
|
QPointF forceVector = ((mouseForceVector * mouseForceStrength) + (Gravity * gravityStrength)) * timeDiff;
|
|
|
|
|
|
emit this->force(forceVector);
|
|
|
|
// has position 0,0 (need soffset)
|
|
QPointF rotatedEndCap = rotate(QPointF(0, length), angle);
|
|
|
|
QPointF forcedCap = rotatedEndCap + forceVector;
|
|
|
|
|
|
// fix the distance to the origin (should be exactly length)
|
|
//double forcedCapLength = lengthOfPoint(forcedCap);
|
|
//forcedCap *= length / forcedCapLength;
|
|
//qDebug() << "normalized:" << forcedCap;
|
|
|
|
// calculate angle
|
|
double newAngle = clampDegrees(pointToAngle(forcedCap));
|
|
// qDebug() << "force:" << forceVector << "forcedCap:" << forcedCap << "angle:" << newAngle;
|
|
|
|
double angleDiff = clampAngleDiff(newAngle - angle);
|
|
|
|
if(qAbs(angleDiff) < 0.01 * timeDiff) {
|
|
lastPosition = newPos;
|
|
lastTimePoint = currentTimePoint;
|
|
return;
|
|
}
|
|
|
|
newAngle += angleVelocity * timeDiff;
|
|
|
|
angleVelocity += angleDiff / timeDiff;
|
|
qDebug() << "angleDiff:" << angleDiff << "angleVelocity:" << angleVelocity << "angle:" << newAngle;
|
|
|
|
angleVelocity *= 0.95;
|
|
if(qAbs(angleVelocity) < 0.001) {
|
|
angleVelocity = 0.0;
|
|
}
|
|
|
|
// calculate offset ( is this required? )
|
|
// QPointF realCap = forcedCap + newPos;
|
|
angle = clampDegrees(newAngle);
|
|
|
|
// just rotate
|
|
// angle += 300 * timeDiff;
|
|
|
|
emit this->angleUpdated(angle);
|
|
|
|
lastPosition = newPos;
|
|
lastTimePoint = currentTimePoint;
|
|
|
|
rerender();
|
|
}
|
|
|
|
void Rotator::rerender() {
|
|
int size = 100;
|
|
|
|
QPixmap px(size, size);
|
|
px.fill(QColorConstants::Transparent);
|
|
|
|
QPainter painter(&px);
|
|
|
|
QTransform transform;
|
|
qreal scaleF = 0.7;
|
|
qreal tX = 16 * (size/32.0);
|
|
qreal tY = 12 * (size/32.0);
|
|
qreal lateTranslate = size/2.0;
|
|
transform.translate(lateTranslate, lateTranslate).scale(scaleF, scaleF).rotate(angle).translate(-tX, -tY);
|
|
painter.setTransform(transform, false);
|
|
renderer->render(&painter);
|
|
|
|
painter.setPen({QColorConstants::Red});
|
|
painter.drawLine(tX-10, tY, tX+10, tY);
|
|
painter.drawLine(tX, tY-10, tX, tY+10);
|
|
painter.setTransform(QTransform(), false);
|
|
painter.drawRect(0, 0, size-1, size-1);
|
|
|
|
emit this->newImage(px);
|
|
|
|
// setCursor(px, {50, 50});
|
|
}
|
|
|
|
void Rotator::setCursor(const QPixmap& px, const QPointF& hotspot) {
|
|
// Get the raw XImage from the QPixmap
|
|
QImage image = px.toImage();
|
|
uint32_t imageSize = image.width() * image.depth() * image.height();
|
|
char* imageData = (char*) malloc(imageSize);
|
|
memcpy(imageData, image.bits(), imageSize);
|
|
XImage* xImage = XCreateImage(
|
|
disp,
|
|
DefaultVisual(disp, DefaultScreen(disp)),
|
|
DefaultDepth(disp, DefaultScreen(disp)),
|
|
ZPixmap, 0, imageData,
|
|
image.width(), image.height(),
|
|
image.depth(), 0
|
|
);
|
|
|
|
// Create the XcursorImages
|
|
XcursorImage* cursorImage = XcursorImageCreate(image.width(), image.height());
|
|
cursorImage->xhot = hotspot.x();
|
|
cursorImage->yhot = hotspot.y();
|
|
|
|
// Copy the image data to XcursorImage
|
|
cursorImage->pixels = (XcursorPixel*) xImage->data;
|
|
cursorImage->delay = 0;
|
|
|
|
// Create the cursor
|
|
Cursor cursor = XcursorImageLoadCursor(disp, cursorImage);
|
|
|
|
// Set the cursor
|
|
XDefineCursor(disp, DefaultRootWindow(disp), cursor);
|
|
|
|
// Clean up resources
|
|
// XFreeCursor(disp, cursor);
|
|
// XDestroyImage(xImage);
|
|
}
|
|
|
|
void Rotator::forAllWindows(std::function<void(Window)> func, Window root)
|
|
{
|
|
func(root);
|
|
|
|
Window* children = nullptr;
|
|
Window dummy;
|
|
unsigned int nchildren;
|
|
if (!XQueryTree(disp, root, &dummy, &dummy, &children, &nchildren)) {
|
|
// no children
|
|
return;
|
|
}
|
|
|
|
for (unsigned int i = 0; i < nchildren; i++) {
|
|
forAllWindows(func, children[i]);
|
|
}
|
|
|
|
if (children) {
|
|
XFree((char*) children);
|
|
}
|
|
}
|