Compare commits

...

30 Commits

Author SHA1 Message Date
Eli Ribble 0d4574f205 Add working server example. 2024-07-30 19:44:32 -07:00
Eli Ribble 9869fd6718 Working Makefile for client program. 2024-07-30 19:44:32 -07:00
Eli Ribble 5357c54180 Add sample client files from capnproto.
Still working on the build logic.
2024-07-30 19:44:32 -07:00
Eli Ribble 33c73c8060 Use a proper file extension for C++
These don't exist on Linux, but they are a fun fiction.
2024-07-30 19:44:32 -07:00
Eli Ribble 694ef2a466 Switch (badly) to C++
This is so I can use capnproto without adding a 3rd party compiler.
2024-07-30 19:44:32 -07:00
Eli Ribble a461c8eb6a Remove uinput-test.
I just used it to learn stuff, don't need it now.
2024-07-30 19:44:32 -07:00
Eli Ribble a88c7c654a Use perror instead of our own crapily-implemented version.
Yay standards.
2024-07-30 19:44:32 -07:00
Eli Ribble 8985f990cb Switch from storing total seconds to storing deltas.
This is conceptually simpler and makes the files easier to manipulate and
concatenate. It also avoids a bug where we would send a large negative
time when we loop for multiple playbacks.
2024-07-30 19:44:32 -07:00
Eli Ribble 0d0d14cc39 Add logic to loop and additional error information on sleep failure. 2024-07-30 19:44:32 -07:00
Eli Ribble a3cb632170 Get python versions up-to-date.
I can't remember now why I did all this, but I did. It's fine.
2024-07-30 19:44:32 -07:00
Eli Ribble 7664f34de5 Drop events from the keyboard of type ...
Not sure what, can't remember, can't be bothered to look it up.
2024-07-30 19:44:32 -07:00
Eli Ribble d64a981c76 Wait 3 seconds to start playback.
Then I can get the game set up.
2024-07-30 19:44:32 -07:00
Eli Ribble 0c3ae88b5a Invert mouse playback.
Without this we go exactly the wrong way in the Y direction. This is likely an
issue with my game rather than the playback.
2024-07-30 19:44:32 -07:00
Eli Ribble e65e442276 Add support for hotkey on capture.
We need this so that we can ensure to line up where our character is before we
get going.
2024-07-30 19:44:32 -07:00
Eli Ribble a3579fd339 Add support for playing back keyboard messages.
Also fix a timing bug where I was waiting 1 second too long
any time the nansec clock rolled over.
2024-07-30 19:44:32 -07:00
Eli Ribble 829432d443 Make mouse playback work.
This only includes left mouse button clicks, not right mouse button
clicks or scrollwheels.
2024-07-30 19:44:32 -07:00
Eli Ribble 61843f23c3 Add sleep logic in playback.
This also removes the "." between the seconds and nanos so that we don't mistake it
for a float, which it isn't.
2024-07-30 19:44:32 -07:00
Eli Ribble 6ed5162e8e Add playback implementation in C.
It doesn't do anything but read lines yet.
2024-07-30 19:44:32 -07:00
Eli Ribble 4a0e7834b3 Flush stdout so our file gets written immediately 2024-07-30 19:44:32 -07:00
Eli Ribble 165623fa20 Add basic Makefile and output directory 2024-07-30 19:44:32 -07:00
Eli Ribble ccc44b676c Show event content for keyboard. 2024-07-30 19:44:32 -07:00
Eli Ribble 54201de47b Add epoll implementation to capture keyboard and mouse.
Yay, double capture!
2024-07-30 19:44:32 -07:00
Eli Ribble ce83c74991 Add working beginnings of capture program.
Only does a single mouse at this point, but it's super fast and the resolution
is perfect.
2024-07-30 19:44:32 -07:00
Eli Ribble 692772f809 Add working C program to get tiny mouse movements.
Turns out that the Python mouse library is losing the fine-grained mouse
position data, which we really need.
2024-07-30 19:44:32 -07:00
Eli Ribble 02f04b78a6 Show mouse raw input from mouse library.
I'm missing a bunch of precision which is no good for capture.
2024-07-30 19:44:32 -07:00
Eli Ribble 71553e0648 Add my uinput test program.
I used this to debug the interations with the kernel module in
the Python code and determine which structs or constants had changed.
2024-07-30 19:44:32 -07:00
Eli Ribble 76f9b495e2 Add a short program to dump key information. 2024-07-30 19:44:32 -07:00
Eli Ribble 17bcf23de2 Add initial gitignore 2024-07-30 19:44:32 -07:00
Eli Ribble 342926610b Add initial working implementation of capture and playback.
This only works on my system with my modified version of
the mouse library.
2024-07-30 19:44:32 -07:00
Eli Ribble ff8955506d Add initial README. 2024-07-30 19:44:30 -07:00
14 changed files with 1512 additions and 1 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
bin/
ve/

13
Makefile Normal file
View File

@ -0,0 +1,13 @@
all: capture playback
bin:
mkdir -p bin
capture: bin capture.cpp
g++ capture.c -o bin/capture
clean:
rm -Rf bin
playback: bin playback.cpp
g++ playback.c -o bin/playback

View File

@ -1,3 +1,5 @@
# symkey
A simple play tool for capturing keyboard and mouse and replaying them. Useful for Hypixel Skyblock
This is a quick-and-dirty project for programmatically controlling keyboard and mouse.
I use it primarily to autoplay Minecraft on Hypixel Skyblock.
Shhh, don't tell.

View File

@ -0,0 +1,20 @@
all: client
CAPNPROTO=/home/eliribble/src/capnproto
CXX=g++
LIBS=\
kj \
kj-async \
capnp \
capnp-rpc
calculator.capnp.h: calculator.capnp
capnp compile -oc++ calculator.capnp
CLIENT_SRCS=calculator-client.c++ calculator.capnp.c++
client: $(CLIENT_SRCS) calculator.capnp.h
$(CXX) $(CLIENT_SRCS) -L $(CAPNPROTO)/c++/.libs $(addprefix -l,$(LIBS)) -o client
clean:
rm client

View File

@ -0,0 +1,367 @@
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include "calculator.capnp.h"
#include <capnp/ez-rpc.h>
#include <kj/debug.h>
#include <math.h>
#include <iostream>
class PowerFunction final: public Calculator::Function::Server {
// An implementation of the Function interface wrapping pow(). Note that
// we're implementing this on the client side and will pass a reference to
// the server. The server will then be able to make calls back to the client.
public:
kj::Promise<void> call(CallContext context) {
auto params = context.getParams().getParams();
KJ_REQUIRE(params.size() == 2, "Wrong number of parameters.");
context.getResults().setValue(pow(params[0], params[1]));
return kj::READY_NOW;
}
};
int main(int argc, const char* argv[]) {
if (argc != 2) {
std::cerr << "usage: " << argv[0] << " HOST:PORT\n"
"Connects to the Calculator server at the given address and "
"does some RPCs." << std::endl;
return 1;
}
capnp::EzRpcClient client(argv[1]);
Calculator::Client calculator = client.getMain<Calculator>();
// Keep an eye on `waitScope`. Whenever you see it used is a place where we
// stop and wait for the server to respond. If a line of code does not use
// `waitScope`, then it does not block!
auto& waitScope = client.getWaitScope();
{
// Make a request that just evaluates the literal value 123.
//
// What's interesting here is that evaluate() returns a "Value", which is
// another interface and therefore points back to an object living on the
// server. We then have to call read() on that object to read it.
// However, even though we are making two RPC's, this block executes in
// *one* network round trip because of promise pipelining: we do not wait
// for the first call to complete before we send the second call to the
// server.
std::cout << "Evaluating a literal... ";
std::cout.flush();
// Set up the request.
auto request = calculator.evaluateRequest();
request.getExpression().setLiteral(123);
// Send it, which returns a promise for the result (without blocking).
auto evalPromise = request.send();
// Using the promise, create a pipelined request to call read() on the
// returned object, and then send that.
auto readPromise = evalPromise.getValue().readRequest().send();
// Now that we've sent all the requests, wait for the response. Until this
// point, we haven't waited at all!
auto response = readPromise.wait(waitScope);
KJ_ASSERT(response.getValue() == 123);
std::cout << "PASS" << std::endl;
}
{
// Make a request to evaluate 123 + 45 - 67.
//
// The Calculator interface requires that we first call getOperator() to
// get the addition and subtraction functions, then call evaluate() to use
// them. But, once again, we can get both functions, call evaluate(), and
// then read() the result -- four RPCs -- in the time of *one* network
// round trip, because of promise pipelining.
std::cout << "Using add and subtract... ";
std::cout.flush();
Calculator::Function::Client add = nullptr;
Calculator::Function::Client subtract = nullptr;
{
// Get the "add" function from the server.
auto request = calculator.getOperatorRequest();
request.setOp(Calculator::Operator::ADD);
add = request.send().getFunc();
}
{
// Get the "subtract" function from the server.
auto request = calculator.getOperatorRequest();
request.setOp(Calculator::Operator::SUBTRACT);
subtract = request.send().getFunc();
}
// Build the request to evaluate 123 + 45 - 67.
auto request = calculator.evaluateRequest();
auto subtractCall = request.getExpression().initCall();
subtractCall.setFunction(subtract);
auto subtractParams = subtractCall.initParams(2);
subtractParams[1].setLiteral(67);
auto addCall = subtractParams[0].initCall();
addCall.setFunction(add);
auto addParams = addCall.initParams(2);
addParams[0].setLiteral(123);
addParams[1].setLiteral(45);
// Send the evaluate() request, read() the result, and wait for read() to
// finish.
auto evalPromise = request.send();
auto readPromise = evalPromise.getValue().readRequest().send();
auto response = readPromise.wait(waitScope);
KJ_ASSERT(response.getValue() == 101);
std::cout << "PASS" << std::endl;
}
{
// Make a request to evaluate 4 * 6, then use the result in two more
// requests that add 3 and 5.
//
// Since evaluate() returns its result wrapped in a `Value`, we can pass
// that `Value` back to the server in subsequent requests before the first
// `evaluate()` has actually returned. Thus, this example again does only
// one network round trip.
std::cout << "Pipelining eval() calls... ";
std::cout.flush();
Calculator::Function::Client add = nullptr;
Calculator::Function::Client multiply = nullptr;
{
// Get the "add" function from the server.
auto request = calculator.getOperatorRequest();
request.setOp(Calculator::Operator::ADD);
add = request.send().getFunc();
}
{
// Get the "multiply" function from the server.
auto request = calculator.getOperatorRequest();
request.setOp(Calculator::Operator::MULTIPLY);
multiply = request.send().getFunc();
}
// Build the request to evaluate 4 * 6
auto request = calculator.evaluateRequest();
auto multiplyCall = request.getExpression().initCall();
multiplyCall.setFunction(multiply);
auto multiplyParams = multiplyCall.initParams(2);
multiplyParams[0].setLiteral(4);
multiplyParams[1].setLiteral(6);
auto multiplyResult = request.send().getValue();
// Use the result in two calls that add 3 and add 5.
auto add3Request = calculator.evaluateRequest();
auto add3Call = add3Request.getExpression().initCall();
add3Call.setFunction(add);
auto add3Params = add3Call.initParams(2);
add3Params[0].setPreviousResult(multiplyResult);
add3Params[1].setLiteral(3);
auto add3Promise = add3Request.send().getValue().readRequest().send();
auto add5Request = calculator.evaluateRequest();
auto add5Call = add5Request.getExpression().initCall();
add5Call.setFunction(add);
auto add5Params = add5Call.initParams(2);
add5Params[0].setPreviousResult(multiplyResult);
add5Params[1].setLiteral(5);
auto add5Promise = add5Request.send().getValue().readRequest().send();
// Now wait for the results.
KJ_ASSERT(add3Promise.wait(waitScope).getValue() == 27);
KJ_ASSERT(add5Promise.wait(waitScope).getValue() == 29);
std::cout << "PASS" << std::endl;
}
{
// Our calculator interface supports defining functions. Here we use it
// to define two functions and then make calls to them as follows:
//
// f(x, y) = x * 100 + y
// g(x) = f(x, x + 1) * 2;
// f(12, 34)
// g(21)
//
// Once again, the whole thing takes only one network round trip.
std::cout << "Defining functions... ";
std::cout.flush();
Calculator::Function::Client add = nullptr;
Calculator::Function::Client multiply = nullptr;
Calculator::Function::Client f = nullptr;
Calculator::Function::Client g = nullptr;
{
// Get the "add" function from the server.
auto request = calculator.getOperatorRequest();
request.setOp(Calculator::Operator::ADD);
add = request.send().getFunc();
}
{
// Get the "multiply" function from the server.
auto request = calculator.getOperatorRequest();
request.setOp(Calculator::Operator::MULTIPLY);
multiply = request.send().getFunc();
}
{
// Define f.
auto request = calculator.defFunctionRequest();
request.setParamCount(2);
{
// Build the function body.
auto addCall = request.getBody().initCall();
addCall.setFunction(add);
auto addParams = addCall.initParams(2);
addParams[1].setParameter(1); // y
auto multiplyCall = addParams[0].initCall();
multiplyCall.setFunction(multiply);
auto multiplyParams = multiplyCall.initParams(2);
multiplyParams[0].setParameter(0); // x
multiplyParams[1].setLiteral(100);
}
f = request.send().getFunc();
}
{
// Define g.
auto request = calculator.defFunctionRequest();
request.setParamCount(1);
{
// Build the function body.
auto multiplyCall = request.getBody().initCall();
multiplyCall.setFunction(multiply);
auto multiplyParams = multiplyCall.initParams(2);
multiplyParams[1].setLiteral(2);
auto fCall = multiplyParams[0].initCall();
fCall.setFunction(f);
auto fParams = fCall.initParams(2);
fParams[0].setParameter(0);
auto addCall = fParams[1].initCall();
addCall.setFunction(add);
auto addParams = addCall.initParams(2);
addParams[0].setParameter(0);
addParams[1].setLiteral(1);
}
g = request.send().getFunc();
}
// OK, we've defined all our functions. Now create our eval requests.
// f(12, 34)
auto fEvalRequest = calculator.evaluateRequest();
auto fCall = fEvalRequest.initExpression().initCall();
fCall.setFunction(f);
auto fParams = fCall.initParams(2);
fParams[0].setLiteral(12);
fParams[1].setLiteral(34);
auto fEvalPromise = fEvalRequest.send().getValue().readRequest().send();
// g(21)
auto gEvalRequest = calculator.evaluateRequest();
auto gCall = gEvalRequest.initExpression().initCall();
gCall.setFunction(g);
gCall.initParams(1)[0].setLiteral(21);
auto gEvalPromise = gEvalRequest.send().getValue().readRequest().send();
// Wait for the results.
KJ_ASSERT(fEvalPromise.wait(waitScope).getValue() == 1234);
KJ_ASSERT(gEvalPromise.wait(waitScope).getValue() == 4244);
std::cout << "PASS" << std::endl;
}
{
// Make a request that will call back to a function defined locally.
//
// Specifically, we will compute 2^(4 + 5). However, exponent is not
// defined by the Calculator server. So, we'll implement the Function
// interface locally and pass it to the server for it to use when
// evaluating the expression.
//
// This example requires two network round trips to complete, because the
// server calls back to the client once before finishing. In this
// particular case, this could potentially be optimized by using a tail
// call on the server side -- see CallContext::tailCall(). However, to
// keep the example simpler, we haven't implemented this optimization in
// the sample server.
std::cout << "Using a callback... ";
std::cout.flush();
Calculator::Function::Client add = nullptr;
{
// Get the "add" function from the server.
auto request = calculator.getOperatorRequest();
request.setOp(Calculator::Operator::ADD);
add = request.send().getFunc();
}
// Build the eval request for 2^(4+5).
auto request = calculator.evaluateRequest();
auto powCall = request.getExpression().initCall();
powCall.setFunction(kj::heap<PowerFunction>());
auto powParams = powCall.initParams(2);
powParams[0].setLiteral(2);
auto addCall = powParams[1].initCall();
addCall.setFunction(add);
auto addParams = addCall.initParams(2);
addParams[0].setLiteral(4);
addParams[1].setLiteral(5);
// Send the request and wait.
auto response = request.send().getValue().readRequest()
.send().wait(waitScope);
KJ_ASSERT(response.getValue() == 512);
std::cout << "PASS" << std::endl;
}
return 0;
}

View File

@ -0,0 +1,215 @@
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include "calculator.capnp.h"
#include <kj/debug.h>
#include <capnp/ez-rpc.h>
#include <capnp/message.h>
#include <iostream>
typedef unsigned int uint;
kj::Promise<double> readValue(Calculator::Value::Client value) {
// Helper function to asynchronously call read() on a Calculator::Value and
// return a promise for the result. (In the future, the generated code might
// include something like this automatically.)
return value.readRequest().send()
.then([](capnp::Response<Calculator::Value::ReadResults> result) {
return result.getValue();
});
}
kj::Promise<double> evaluateImpl(
Calculator::Expression::Reader expression,
capnp::List<double>::Reader params = capnp::List<double>::Reader()) {
// Implementation of CalculatorImpl::evaluate(), also shared by
// FunctionImpl::call(). In the latter case, `params` are the parameter
// values passed to the function; in the former case, `params` is just an
// empty list.
switch (expression.which()) {
case Calculator::Expression::LITERAL:
return expression.getLiteral();
case Calculator::Expression::PREVIOUS_RESULT:
return readValue(expression.getPreviousResult());
case Calculator::Expression::PARAMETER: {
KJ_REQUIRE(expression.getParameter() < params.size(),
"Parameter index out-of-range.");
return params[expression.getParameter()];
}
case Calculator::Expression::CALL: {
auto call = expression.getCall();
auto func = call.getFunction();
// Evaluate each parameter.
kj::Array<kj::Promise<double>> paramPromises =
KJ_MAP(param, call.getParams()) {
return evaluateImpl(param, params);
};
// Join the array of promises into a promise for an array.
kj::Promise<kj::Array<double>> joinedParams =
kj::joinPromises(kj::mv(paramPromises));
// When the parameters are complete, call the function.
return joinedParams.then([KJ_CPCAP(func)](kj::Array<double>&& paramValues) mutable {
auto request = func.callRequest();
request.setParams(paramValues);
return request.send().then(
[](capnp::Response<Calculator::Function::CallResults>&& result) {
return result.getValue();
});
});
}
default:
// Throw an exception.
KJ_FAIL_REQUIRE("Unknown expression type.");
}
}
class ValueImpl final: public Calculator::Value::Server {
// Simple implementation of the Calculator.Value Cap'n Proto interface.
public:
ValueImpl(double value): value(value) {}
kj::Promise<void> read(ReadContext context) {
context.getResults().setValue(value);
return kj::READY_NOW;
}
private:
double value;
};
class FunctionImpl final: public Calculator::Function::Server {
// Implementation of the Calculator.Function Cap'n Proto interface, where the
// function is defined by a Calculator.Expression.
public:
FunctionImpl(uint paramCount, Calculator::Expression::Reader body)
: paramCount(paramCount) {
this->body.setRoot(body);
}
kj::Promise<void> call(CallContext context) {
auto params = context.getParams().getParams();
KJ_REQUIRE(params.size() == paramCount, "Wrong number of parameters.");
return evaluateImpl(body.getRoot<Calculator::Expression>(), params)
.then([KJ_CPCAP(context)](double value) mutable {
context.getResults().setValue(value);
});
}
private:
uint paramCount;
// The function's arity.
capnp::MallocMessageBuilder body;
// Stores a permanent copy of the function body.
};
class OperatorImpl final: public Calculator::Function::Server {
// Implementation of the Calculator.Function Cap'n Proto interface, wrapping
// basic binary arithmetic operators.
public:
OperatorImpl(Calculator::Operator op): op(op) {}
kj::Promise<void> call(CallContext context) {
auto params = context.getParams().getParams();
KJ_REQUIRE(params.size() == 2, "Wrong number of parameters.");
double result;
switch (op) {
case Calculator::Operator::ADD: result = params[0] + params[1]; break;
case Calculator::Operator::SUBTRACT:result = params[0] - params[1]; break;
case Calculator::Operator::MULTIPLY:result = params[0] * params[1]; break;
case Calculator::Operator::DIVIDE: result = params[0] / params[1]; break;
default:
KJ_FAIL_REQUIRE("Unknown operator.");
}
context.getResults().setValue(result);
return kj::READY_NOW;
}
private:
Calculator::Operator op;
};
class CalculatorImpl final: public Calculator::Server {
// Implementation of the Calculator Cap'n Proto interface.
public:
kj::Promise<void> evaluate(EvaluateContext context) override {
return evaluateImpl(context.getParams().getExpression())
.then([KJ_CPCAP(context)](double value) mutable {
context.getResults().setValue(kj::heap<ValueImpl>(value));
});
}
kj::Promise<void> defFunction(DefFunctionContext context) override {
auto params = context.getParams();
context.getResults().setFunc(kj::heap<FunctionImpl>(
params.getParamCount(), params.getBody()));
return kj::READY_NOW;
}
kj::Promise<void> getOperator(GetOperatorContext context) override {
context.getResults().setFunc(kj::heap<OperatorImpl>(
context.getParams().getOp()));
return kj::READY_NOW;
}
};
int main(int argc, const char* argv[]) {
if (argc != 2) {
std::cerr << "usage: " << argv[0] << " ADDRESS[:PORT]\n"
"Runs the server bound to the given address/port.\n"
"ADDRESS may be '*' to bind to all local addresses.\n"
":PORT may be omitted to choose a port automatically." << std::endl;
return 1;
}
// Set up a server.
capnp::EzRpcServer server(kj::heap<CalculatorImpl>(), argv[1]);
// Write the port number to stdout, in case it was chosen automatically.
auto& waitScope = server.getWaitScope();
uint port = server.getPort().wait(waitScope);
if (port == 0) {
// The address format "unix:/path/to/socket" opens a unix domain socket,
// in which case the port will be zero.
std::cout << "Listening on Unix socket..." << std::endl;
} else {
std::cout << "Listening on port " << port << "..." << std::endl;
}
// Run forever, accepting connections and handling requests.
kj::NEVER_DONE.wait(waitScope);
}

View File

@ -0,0 +1,118 @@
# Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
# Licensed under the MIT License:
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
@0x85150b117366d14b;
interface Calculator {
# A "simple" mathematical calculator, callable via RPC.
#
# But, to show off Cap'n Proto, we add some twists:
#
# - You can use the result from one call as the input to the next
# without a network round trip. To accomplish this, evaluate()
# returns a `Value` object wrapping the actual numeric value.
# This object may be used in a subsequent expression. With
# promise pipelining, the Value can actually be used before
# the evaluate() call that creates it returns!
#
# - You can define new functions, and then call them. This again
# shows off pipelining, but it also gives the client the
# opportunity to define a function on the client side and have
# the server call back to it.
#
# - The basic arithmetic operators are exposed as Functions, and
# you have to call getOperator() to obtain them from the server.
# This again demonstrates pipelining -- using getOperator() to
# get each operator and then using them in evaluate() still
# only takes one network round trip.
evaluate @0 (expression :Expression) -> (value :Value);
# Evaluate the given expression and return the result. The
# result is returned wrapped in a Value interface so that you
# may pass it back to the server in a pipelined request. To
# actually get the numeric value, you must call read() on the
# Value -- but again, this can be pipelined so that it incurs
# no additional latency.
struct Expression {
# A numeric expression.
union {
literal @0 :Float64;
# A literal numeric value.
previousResult @1 :Value;
# A value that was (or, will be) returned by a previous
# evaluate().
parameter @2 :UInt32;
# A parameter to the function (only valid in function bodies;
# see defFunction).
call :group {
# Call a function on a list of parameters.
function @3 :Function;
params @4 :List(Expression);
}
}
}
interface Value {
# Wraps a numeric value in an RPC object. This allows the value
# to be used in subsequent evaluate() requests without the client
# waiting for the evaluate() that returns the Value to finish.
read @0 () -> (value :Float64);
# Read back the raw numeric value.
}
defFunction @1 (paramCount :Int32, body :Expression)
-> (func :Function);
# Define a function that takes `paramCount` parameters and returns the
# evaluation of `body` after substituting these parameters.
interface Function {
# An algebraic function. Can be called directly, or can be used inside
# an Expression.
#
# A client can create a Function that runs on the server side using
# `defFunction()` or `getOperator()`. Alternatively, a client can
# implement a Function on the client side and the server will call back
# to it. However, a function defined on the client side will require a
# network round trip whenever the server needs to call it, whereas
# functions defined on the server and then passed back to it are called
# locally.
call @0 (params :List(Float64)) -> (value :Float64);
# Call the function on the given parameters.
}
getOperator @2 (op :Operator) -> (func :Function);
# Get a Function representing an arithmetic operator, which can then be
# used in Expressions.
enum Operator {
add @0;
subtract @1;
multiply @2;
divide @3;
}
}

220
capture.cpp Normal file
View File

@ -0,0 +1,220 @@
#include <errno.h>
#include <fcntl.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <time.h>
#include <unistd.h>
#include <linux/input.h>
#include <sys/epoll.h>
#define MAX_EVENTS 16
#define CONTENT_BUFFER_SIZE 32
constexpr std::string_view KEYBOARD = "k";
constexpr std::string_view MOUSE = "m";
int dump_event(struct timespec* start, struct input_event* event, const std::string_view& type);
int event_content_keyboard(char* buffer, int buffer_size, struct input_event* event);
int event_content_mouse(char* buffer, int buffer_size, struct input_event* event);
int stream_events(char* mouse_path, char* keyboard_path, int hotkey_scancode);
static inline void timespec_diff(struct timespec* a, struct timespec* b, struct timespec* result) {
result->tv_sec = a->tv_sec - b->tv_sec;
result->tv_nsec = a->tv_nsec - b->tv_nsec;
if (result->tv_nsec < 0) {
--result->tv_sec;
result->tv_nsec += 1000000000L;
}
}
static inline void timeval_diff(struct timeval* a, struct timeval* b, struct timeval* result) {
result->tv_sec = a->tv_sec - b->tv_sec;
result->tv_usec = a->tv_usec - b->tv_usec;
if (result->tv_usec < 0) {
--result->tv_sec;
result->tv_usec += 1000000L;
}
}
static inline void time_diff(struct timeval* a, struct timespec* b, struct timeval* result) {
result->tv_sec = a->tv_sec - b->tv_sec;
result->tv_usec = a->tv_usec - (b->tv_nsec / 1000);
if (result->tv_usec < 0) {
--result->tv_sec;
result->tv_usec += 1000000L;
}
}
int dump_event(struct timespec* start, struct input_event* event, const std::string_view& type) {
struct timespec now;
struct timespec diff;
char content_buffer[CONTENT_BUFFER_SIZE];
clock_gettime(CLOCK_MONOTONIC, &now);
timespec_diff(&now, start, &diff);
// time_diff(&(event.time), start, &diff);
if(type == MOUSE) {
if(event_content_mouse(content_buffer, CONTENT_BUFFER_SIZE, event)) {
return 1;
}
} else if(type == KEYBOARD) {
// Ignore all but EV_KEY events on keyboard, they have no useful content.
if(event->type != EV_KEY) {
return 0;
}
if(event_content_keyboard(content_buffer, CONTENT_BUFFER_SIZE, event)) {
return 1;
}
} else {
fprintf(stderr, "Unknown event type.\n");
return 1;
}
start->tv_sec = now.tv_sec;
start->tv_nsec = now.tv_nsec;
std::cout <<
diff.tv_sec << " " <<
diff.tv_nsec << "," <<
type << "," <<
content_buffer << std::endl;
fflush(stdout);
return 0;
}
int event_content_keyboard(char* buffer, int buffer_size, struct input_event* event) {
sprintf(buffer, "%d,%d,%d",
event->type,
event->code,
event->value);
return 0;
}
int event_content_mouse(char* buffer, int buffer_size, struct input_event* event) {
unsigned char button, bLeft, bMiddle, bRight;
unsigned char *ptr = (unsigned char*)event;
int i;
char x, y;
button = ptr[0];
bLeft = button & 0x1;
bMiddle = ( button & 0x4 ) > 0;
bRight = ( button & 0x2 ) > 0;
x=(char) ptr[1];
y=(char) ptr[2];
int chars = sprintf(buffer, "l%d,m%d,r%d,x%d,y%d",
bLeft, bMiddle, bRight, x, y);
return chars < 0;
}
int main(int argc, char* argv[]) {
int hotkey_scancode;
if(argc < 2) {
fprintf(stderr, "You must specify a mouse input to track like /dev/input/mouse1.");
exit(EXIT_FAILURE);
}
if(argc < 3) {
fprintf(stderr, "You must specify a keyboard input to track like /dev/input/event3. If you're not sure which to use read through /proc/bus/input/devices and look for 'Handlers=eventX'");
exit(EXIT_FAILURE);
}
if(argc < 4) {
fprintf(stderr, "You must specify a character to indicate when to start and stop capture. 53 for 'z'.\n");
exit(EXIT_FAILURE);
}
int matched = sscanf(argv[3], "%d", &hotkey_scancode);
if(matched != 1) {
fprintf(stderr, "Failed to read hotkey scancode.\n");
exit(EXIT_FAILURE);
}
int result = stream_events(argv[1], argv[2], hotkey_scancode);
exit(result);
}
int stream_events(char* mouse_path, char* keyboard_path, int hotkey_scancode) {
int keyboard_fd, mouse_fd;
struct timespec start;
struct epoll_event e_event, events[MAX_EVENTS];
int has_seen_hotkey = 0;
int running = 1;
int epoll_fd = epoll_create1(0);
if(epoll_fd < 0) {
fprintf(stderr, "Failed to initialize epoll fd: %d", errno);
return 1;
}
if((mouse_fd = open(mouse_path, O_RDONLY)) == -1) {
fprintf(stderr, "Failed to open %s. Are you running as root?\n", mouse_path);
return 1;
}
else {
fprintf(stderr, "%s open OK\n", mouse_path);
}
if((keyboard_fd = open(keyboard_path, O_RDONLY)) == -1) {
fprintf(stderr, "Failed to open %s. Are you running as root?\n", keyboard_path);
return 1;
}
else {
fprintf(stderr, "%s open OK\n", keyboard_path);
}
e_event.events = EPOLLIN;
e_event.data.fd = keyboard_fd;
if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, keyboard_fd, &e_event)) {
fprintf(stderr, "Failed to add keyboard file descriptor\n");
close(epoll_fd);
return 1;
}
e_event.data.fd = mouse_fd;
if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, mouse_fd, &e_event)) {
fprintf(stderr, "Failed to add mouse file descriptor\n");
close(epoll_fd);
return 1;
}
fprintf(stderr, "Waiting for hotkey\n");
struct input_event i_event;
while(running) {
int event_count = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
std::string_view type;
for(int i = 0; i < event_count; i++) {
int result = read(events[i].data.fd, &i_event, sizeof(struct input_event));
if(result < 0) {
perror("Failed to read an event");
return 1;
}
if(events[i].data.fd == keyboard_fd) {
type = KEYBOARD;
if(i_event.type == EV_KEY && i_event.code == hotkey_scancode && i_event.value == 1) {
if(has_seen_hotkey) {
fprintf(stderr, "Stop capture\n");
return 0;
} else {
has_seen_hotkey = 1;
fprintf(stderr, "Start capture\n");
clock_gettime(CLOCK_MONOTONIC, &start);
continue;
}
} else if(i_event.value == 2) {
continue;
}
} else if (events[i].data.fd == mouse_fd) {
type = MOUSE;
} else {
fprintf(stderr, "Unknown fd");
return 1;
}
// Wait for the hotkey to start capture
if(has_seen_hotkey && dump_event(&start, &i_event, type)) {
return 1;
}
}
}
return 0;
}

102
capture.py Normal file
View File

@ -0,0 +1,102 @@
import argparse
import functools
import keyboard
import logging
import mouse
import pickle
import queue
import sys
import threading
import time
LOGGER = logging.getLogger("capture")
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument(
"-d", "--delay",
default=0,
type=int,
help="Seconds to wait before capture."
)
parser.add_argument(
"-t", "--trigger",
default=None,
help="The key to use to trigger start and stop of capture."
)
parser.add_argument(
"-o", "--output",
default="dump.capture",
help="Name of the file to capture to."
)
parser.add_argument(
"--verbose",
action="store_true",
help="Show verbose logging.",
)
args = parser.parse_args()
logging.basicConfig(
level=logging.DEBUG if args.verbose else logging.INFO)
if args.delay and args.trigger:
print("You cannot specify 'delay' and 'trigger'")
sys.exit(1)
_do_delay(args.delay)
_do_trigger(args.trigger)
LOGGER.info("Capturing...")
now = time.time()
# Add dummy events to lock in the time start
with open(args.output, "w") as output:
hook = functools.partial(_on_hook, now, output)
keyhook = keyboard.hook(hook)
mousehook = mouse.hook(hook)
try:
keyboard.wait(args.trigger)
except KeyboardInterrupt:
keyboard.unhook(keyhook)
mouse.unhook(mousehook)
LOGGER.info("Wrote %s", args.output)
def _on_hook(start, output, event):
LOGGER.debug(str(event))
relative_time = event.time - start
if isinstance(event, keyboard.KeyboardEvent):
output.write(
f"{relative_time},k,{event.event_type},{event.scan_code},{event.name}\n")
elif isinstance(event, mouse.ButtonEvent):
output.write(
f"{relative_time},mb,{event.event_type},{event.button}\n")
elif isinstance(event, mouse.MoveEvent):
output.write(
f"{relative_time},mm,{event.x},{event.y}\n")
elif isinstance(event, mouse.WheelEvent):
output.write(
f"{relative_time},mw,{event.delta}\n")
else:
raise ValueError(f"{event} is not recognized")
def _do_delay(delay: int) -> None:
if not delay:
return
print("\n")
for i in range(delay):
print(f"\rStarting in {delay-i} seconds")
time.sleep(1)
def _do_trigger(trigger: str) -> None:
if not trigger:
return
print(f"Waiting for '{trigger}'")
keyboard.wait(trigger)
def _save_events(event_q: queue.Queue, filename: str) -> None:
events = []
while not event_q.empty():
event = event_q.get(block=False)
events.append(event)
with open(filename, "wb") as output:
pickle.dump(events, output)
print(f"Wrote to {filename}")
if __name__ == "__main__":
main()

261
playback.cpp Normal file
View File

@ -0,0 +1,261 @@
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <linux/uinput.h>
#include <sys/stat.h>
static volatile int is_running = 1;
int handle_udevice(char* details, int udevice_fd);
int handle_line(char* line, int udevice_fd);
int handle_mouse(char* details, int udevice_fd);
int read_file(char* filename, int udevice_fd);
int setup_udevice();
void sigint_handler(int dummy);
void teardown_udevice(int fd);
void emit(int fd, int type, int code, int val) {
struct input_event ie;
ie.type = type;
ie.code = code;
ie.value = val;
ie.time.tv_sec = 0;
ie.time.tv_usec = 0;
write(fd, &ie, sizeof(ie));
}
int handle_keyboard(char* details, int udevice_fd) {
int code, event_type, value;
int matched = sscanf(details, "%d,%d,%d", &event_type, &code, &value);
if(matched != 3) {
printf("Didn't match enough values for a keyboard event.\n");
return 1;
}
// printf("Event type: %d, Code: %d, Value: %d\n",
// event_type, code, value);
if(event_type != 1) {
printf("Not sure what to do with event type %d", event_type);
return 1;
}
emit(udevice_fd, EV_KEY, code, value);
emit(udevice_fd, EV_SYN, SYN_REPORT, 0);
return 0;
}
int handle_line(char* line, int udevice_fd) {
static time_t total_seconds = 0;
static long total_nanos = 0;
time_t seconds;
long nanos;
char type;
char details[32];
struct timespec to_sleep;
struct timespec remaining;
int matched = sscanf(line, "%ld %ld,%c,%s\n", &seconds, &nanos, &type, details);
if(matched == 0) {
printf("Line '%s' appears incorrect. Exiting", line);
return 1;
} else if(matched < 4) {
printf("Only matched %d", matched);
return 1;
}
remaining.tv_sec = 0;
remaining.tv_nsec = 0;
to_sleep.tv_sec = seconds;
to_sleep.tv_nsec = nanos;
if(to_sleep.tv_nsec < 0) {
--to_sleep.tv_sec;
to_sleep.tv_nsec += 1000000000L;
}
// printf("Timer %ld %ld\n", timer_seconds, timer_nanos);
// printf("Read %ld %ld\n", seconds, nanos);
// printf("Sleep %ld %ld\n", to_sleep.tv_sec, to_sleep.tv_nsec);
while(nanosleep(&to_sleep, &remaining) == -1) {
if(!is_running) {
return 0;
}
perror("nanosleep error");
printf("Attempted %ld.%ld sleep\n", to_sleep.tv_sec, to_sleep.tv_nsec);
printf("Need %ld.%ld more seconds for total sleep\n", remaining.tv_sec, remaining.tv_nsec);
to_sleep.tv_sec = remaining.tv_sec;
to_sleep.tv_nsec = remaining.tv_nsec;
}
if(remaining.tv_sec != 0 || remaining.tv_nsec != 0) {
printf("oops, remaining.\n");
}
total_seconds += to_sleep.tv_sec;
total_nanos += to_sleep.tv_nsec;
if(total_nanos > 1000000000L) {
total_nanos -= 1000000000L;
total_seconds += 1;
}
// printf("%ld %ld\tslept %ld %ld\n",
// total_seconds, total_nanos, to_sleep.tv_sec, to_sleep.tv_nsec);
if(type == 'k') {
return handle_keyboard(details, udevice_fd);
} else if(type == 'm') {
return handle_mouse(details, udevice_fd);
} else {
printf("Unexpected type %c/n", type);
return 1;
}
}
int handle_mouse(char* details, int udevice_fd) {
static int current_left = 0;
static int current_middle = 0;
static int current_right = 0;
int left, middle, right, x, y;
int matched = sscanf(details, "l%d,m%d,r%d,x%d,y%d",
&left, &middle, &right, &x, &y);
if(matched != 5) {
printf("Failed to match enough data for a mouse event.\n");
return 1;
}
// printf("L: %d M: %d, R: %d, X: %d, Y: %d\n",
// left, middle, right, x, y);
/* Move the mouse diagonally, 5 units per axis */
if(x != 0) {
emit(udevice_fd, EV_REL, REL_X, x);
}
if(y != 0) {
emit(udevice_fd, EV_REL, REL_Y, -1 * y);
}
if(left != current_left) {
emit(udevice_fd, EV_KEY, BTN_LEFT, left);
current_left = left;
}
if(middle != current_middle) {
emit(udevice_fd, EV_KEY, BTN_MIDDLE, middle);
current_middle = middle;
}
if(right != current_right) {
emit(udevice_fd, EV_KEY, BTN_RIGHT, right);
current_right = right;
}
emit(udevice_fd, EV_SYN, SYN_REPORT, 0);
return 0;
}
int main(int argc, char* argv[]) {
int repeat = 1;
if(argc < 2) {
printf("Please provide a capture file.");
exit(EXIT_FAILURE);
}
if(argc == 3) {
int matched = sscanf(argv[2], "%d", &repeat);
if(matched != 1) {
fprintf(stderr, "Failed to read repeat value.\n");
exit(EXIT_FAILURE);
}
printf("Repeating %d times\n", repeat);
}
signal(SIGINT, sigint_handler);
for(int i = 3; i > 0; i--) {
fprintf(stderr, "Playing back in %d seconds\n", i);
sleep(1);
}
int result = 0;
int udevice_fd = setup_udevice();
for(int i = 0; is_running && i < repeat; i++) {
fprintf(stderr, "Repeat %d/%d\n", i+1, repeat);
if(read_file(argv[1], udevice_fd)) {
result = EXIT_FAILURE;
}
}
teardown_udevice(udevice_fd);
return result;
}
int read_file(char* filename, int udevice_fd) {
FILE* fp;
char* line = NULL;
size_t len = 0;
ssize_t read;
fp = fopen(filename, "r");
if (fp == NULL) {
printf("Failed to open file %s: %d\n", filename, errno);
return 1;
}
while(is_running && (read = getline(&line, &len, fp)) != -1) {
if(handle_line(line, udevice_fd)) {
return 1;
}
}
fclose(fp);
return 0;
}
int setup_udevice() {
struct uinput_setup usetup;
int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
/* enable mouse button left and relative events */
ioctl(fd, UI_SET_EVBIT, EV_KEY);
// Add keyboard keys. We could do this individually but we're super
// lazy and it appears a loop should work fine based on the linux/input-event-codes.h header
for(int i = KEY_ESC; i <= KEY_MICMUTE; i++) {
ioctl(fd, UI_SET_KEYBIT, i);
}
// Add mouse buttons
ioctl(fd, UI_SET_KEYBIT, BTN_LEFT);
ioctl(fd, UI_SET_KEYBIT, BTN_RIGHT);
ioctl(fd, UI_SET_EVBIT, EV_REL);
ioctl(fd, UI_SET_RELBIT, REL_X);
ioctl(fd, UI_SET_RELBIT, REL_Y);
memset(&usetup, 0, sizeof(usetup));
usetup.id.bustype = BUS_USB;
usetup.id.vendor = 0x1234; /* sample vendor */
usetup.id.product = 0x5678; /* sample product */
strcpy(usetup.name, "Playback mouses");
ioctl(fd, UI_DEV_SETUP, &usetup);
ioctl(fd, UI_DEV_CREATE);
/*
* On UI_DEV_CREATE the kernel will create the device node for this
* device. We are inserting a pause here so that userspace has time
* to detect, initialize the new device, and can start listening to
* the event, otherwise it will not notice the event we are about
* to send. This pause is only needed in our example code!
*/
// sleep(1);
return fd;
}
void sigint_handler(int dummy) {
is_running = 0;
}
void teardown_udevice(int fd) {
ioctl(fd, UI_DEV_DESTROY);
close(fd);
}

96
playback.py Normal file
View File

@ -0,0 +1,96 @@
import argparse
import keyboard
import logging
import mouse
import pickle
import threading
import time
LOGGER = logging.getLogger("playback")
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument(
"-d", "--delay",
default=5,
type=int,
help="Seconds to wait before replay."
)
parser.add_argument(
"-i", "--input",
default="dump.capture",
help="Name of the file to replay from."
)
parser.add_argument(
"--verbose",
action="store_true",
help="Show verbose messages."
)
args = parser.parse_args()
logging.basicConfig(
level = logging.DEBUG if args.verbose else logging.INFO
)
_do_delay(args.delay)
events = _load_events(args.input)
_play_events(events)
def _play_events(events) -> None:
LOGGER.info("Playback started.")
key_state = keyboard.stash_state()
last_time = None
for event in events:
if last_time is not None:
to_sleep = event.time - last_time
if to_sleep > 0:
time.sleep(to_sleep)
last_time = event.time
if isinstance(event, keyboard.KeyboardEvent):
_play_event_keyboard(event)
elif any([
isinstance(event, mouse.ButtonEvent),
isinstance(event, mouse.MoveEvent),
isinstance(event, mouse.WheelEvent),
]):
_play_event_mouse(event)
else:
raise ValueError(f"Not a recognized event {event}")
keyboard.restore_modifiers(key_state)
LOGGER.info("Done.")
def _play_event_keyboard(event) -> None:
LOGGER.debug("Key %s", event)
key = event.scan_code or event.name
keyboard.press(key) if event.event_type == keyboard.KEY_DOWN else keyboard.release(key)
def _play_event_mouse(event) -> None:
LOGGER.debug("Mouse %s", event)
if isinstance(event, mouse.ButtonEvent):
if event.event_type == mouse.UP:
mouse.release(event.button)
else:
mouse.press(event.button)
elif isinstance(event, mouse.MoveEvent):
mouse.move(event.x, event.y, absolute=True)
# mouse.move(event.x, event.y)
elif isinstance(event, mouse.WheelEvent):
mouse.wheel(event.delta)
def _do_delay(delay: int) -> None:
if not delay:
return
print("\n")
for i in range(delay):
print(f"\rStarting in {delay-i} seconds")
time.sleep(1)
def _load_events(filename: str):
with open(filename, "rb") as input_:
events = pickle.load(input_)
LOGGER.debug("Loaded %s", filename)
return events
if __name__ == "__main__":
main()

15
show-keys Executable file
View File

@ -0,0 +1,15 @@
#!env python3
import keyboard
def main() -> None:
keyboard.hook(_on_event)
try:
keyboard.wait()
except KeyboardInterrupt:
print("End.")
def _on_event(event: keyboard.KeyboardEvent) -> None:
print(event.to_json())
if __name__ == "__main__":
main()

19
show-mouse Executable file
View File

@ -0,0 +1,19 @@
#!env python3
import keyboard
import logging
import mouse
def main() -> None:
logging.basicConfig(level=logging.DEBUG)
mouse.hook(_on_event)
try:
mouse.wait()
except KeyboardInterrupt:
pass
print("End.")
def _on_event(event: keyboard.KeyboardEvent) -> None:
print(event)
if __name__ == "__main__":
main()

61
show-mouse.c Normal file
View File

@ -0,0 +1,61 @@
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
#define MOUSEFILE "/dev/input/mouse1"
//
int main()
{
int fd;
struct input_event ie;
//
unsigned char button,bLeft,bMiddle,bRight;
char x,y;
int absolute_x,absolute_y;
if((fd = open(MOUSEFILE, O_RDONLY)) == -1) {
printf("Device open ERROR\n");
exit(EXIT_FAILURE);
}
else
{
printf("Device open OK\n");
}
//
printf("right-click to set absolute x,y coordinates origin (0,0)\n");
while(read(fd, &ie, sizeof(struct input_event)))
{
unsigned char *ptr = (unsigned char*)&ie;
int i;
//
button=ptr[0];
bLeft = button & 0x1;
bMiddle = ( button & 0x4 ) > 0;
bRight = ( button & 0x2 ) > 0;
x=(char) ptr[1];y=(char) ptr[2];
// printf("bLEFT:%d, bMIDDLE: %d, bRIGHT: %d, rx: %d ry=%d\n",bLeft,bMiddle,bRight, x,y);
//
absolute_x+=x;
absolute_y-=y;
if (bRight==1)
{
absolute_x=0;
absolute_y=0;
printf("Absolute x,y coords origin recorded\n");
}
//
printf("Absolute coords from TOP_LEFT= %i %i\n",absolute_x,absolute_y);
//
// comment to disable the display of raw event structure datas
//
for(i=0; i<sizeof(ie); i++)
{
printf("%02X ", *ptr++);
}
printf("\n");
}
return 0;
}