Compare commits
30 Commits
f55fa715f2
...
0d4574f205
Author | SHA1 | Date |
---|---|---|
|
0d4574f205 | |
|
9869fd6718 | |
|
5357c54180 | |
|
33c73c8060 | |
|
694ef2a466 | |
|
a461c8eb6a | |
|
a88c7c654a | |
|
8985f990cb | |
|
0d0d14cc39 | |
|
a3cb632170 | |
|
7664f34de5 | |
|
d64a981c76 | |
|
0c3ae88b5a | |
|
e65e442276 | |
|
a3579fd339 | |
|
829432d443 | |
|
61843f23c3 | |
|
6ed5162e8e | |
|
4a0e7834b3 | |
|
165623fa20 | |
|
ccc44b676c | |
|
54201de47b | |
|
ce83c74991 | |
|
692772f809 | |
|
02f04b78a6 | |
|
71553e0648 | |
|
76f9b495e2 | |
|
17bcf23de2 | |
|
342926610b | |
|
ff8955506d |
|
@ -0,0 +1,2 @@
|
||||||
|
bin/
|
||||||
|
ve/
|
|
@ -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
|
|
@ -1,3 +1,5 @@
|
||||||
# symkey
|
# 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.
|
||||||
|
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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()
|
|
@ -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);
|
||||||
|
}
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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;
|
||||||
|
}
|
Loading…
Reference in New Issue