summaryrefslogtreecommitdiff
path: root/common/rawaccel-io.hpp
blob: 046ac3d8228b6f953283291463e62802c2b75777 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#pragma once

#include "rawaccel-io-def.h"
#include "rawaccel-version.h"
#include "rawaccel-error.hpp"
#include "rawaccel.hpp"

#include <memory>

namespace rawaccel {

    inline void io_control(DWORD code, void* in, DWORD in_size, void* out, DWORD out_size) 
    {
        HANDLE ra_handle = INVALID_HANDLE_VALUE;

        ra_handle = CreateFileW(L"\\\\.\\rawaccel", 0, 0, 0, OPEN_EXISTING, 0, 0);

        if (ra_handle == INVALID_HANDLE_VALUE) {
            throw install_error();
        }

        DWORD dummy;

        BOOL success = DeviceIoControl(
            ra_handle,
            code,
            in,
            in_size,
            out,
            out_size,
            &dummy,  // bytes returned
            NULL     // overlapped structure
        );

        CloseHandle(ra_handle);

        if (!success) {
            throw sys_error("DeviceIoControl failed");
        }
    }

    inline std::unique_ptr<std::byte[]> read()
    {
        io_base base_data;

        io_control(READ, NULL, 0, &base_data, sizeof(io_base));

        size_t size = sizeof(base_data);

        if (base_data.modifier_data_size == 0) {
            // driver has no data, but it's more useful to return something, 
            // so return a default modifier_settings object along with base data
             
            size += sizeof(modifier_settings);
            base_data.modifier_data_size = 1;
            auto bytes = std::make_unique<std::byte[]>(size);
            *reinterpret_cast<io_base*>(bytes.get()) = base_data;
            *reinterpret_cast<modifier_settings*>(bytes.get() + sizeof(io_base)) = {};
            return bytes;
        }
        else {
            size += sizeof(modifier_settings) * base_data.modifier_data_size;
            size += sizeof(device_settings) * base_data.device_data_size;
            auto bytes = std::make_unique<std::byte[]>(size);
            io_control(READ, NULL, 0, bytes.get(), DWORD(size));
            return bytes;
        }
    }

    // buffer must point to at least sizeof(io_base) bytes
    inline void write(const void* buffer)
    {
        if (buffer == nullptr) throw io_error("write buffer is null");

        auto* base_ptr = static_cast<const io_base*>(buffer);
        auto size = sizeof(io_base);
        size += base_ptr->modifier_data_size * sizeof(modifier_settings);
        size += base_ptr->device_data_size * sizeof(device_settings);

        if (size > DWORD(-1)) throw io_error("write buffer is too large");

        io_control(WRITE, const_cast<void*>(buffer), DWORD(size), NULL, 0);
    }

    inline void reset()
    {
        io_base base_data{};
        // all modifier/device data is cleared when a default io_base is passed
        io_control(WRITE, &base_data, sizeof(io_base), NULL, 0);
    }

    inline version_t get_version() 
    {
        version_t v;

        try {
            io_control(GET_VERSION, NULL, 0, &v, sizeof(version_t));
        }
        catch (const sys_error&) {
            // assume request is not implemented (< 1.3)
            v = { 0 }; 
        }

        return v;
    }

    inline version_t valid_version_or_throw()
    {
        auto v = get_version();

        if (v < min_driver_version) {
            throw error("reinstallation required");
        }

        if (version < v) {
            throw error("newer driver is installed");
        }

        return v;
    }

}