Branch data Line data Source code
1 : : #ifdef RUN_TESTS
2 : : # include <gtest/gtest.h>
3 : : #endif
4 : : /**@file autoinput.cc
5 : : * @brief This module provides the same functionality as INPUTTER (https://bisqwit.iki.fi/source/inputter.html)
6 : : */
7 : :
8 : : #include <mutex>
9 : : #include <thread>
10 : : #include <string_view>
11 : : #include <random>
12 : : #include <list>
13 : :
14 : : #include "autoinput.hh"
15 : : #include "ctype.hh"
16 : : #include "clock.hh"
17 : : #include "ui.hh"
18 : :
19 : : //#define TURBOHACK
20 : :
21 : : namespace
22 : : {
23 : : std::mutex lock;
24 : : std::list<AutoInputResponse> data;
25 : : bool terminate = false;
26 : : bool finished = true;
27 : : }
28 : :
29 : : static constexpr unsigned char Delay_CharClassTable[] =
30 : : {
31 : : //Delay_CharClassTable:
32 : : // ; D, H, I, J, M are fast ctrls
33 : : // ; K is CtrlK
34 : : // ; A,B,E,F,W,Y, Z are other ctrls
35 : : //
36 : : // ; Ctrl keys:
37 : : //; @A BC DE FG HI JK LM NO PQ RS TU VW XY Z[ \] ^_
38 : : // ; Normal keys
39 : : //; ! "# $% &' () *+ ,- ./ 01 23 45 67 89 :; <= >?
40 : : //; @A BC DE FG HI JK LM NO PQ RS TU VW XY Z[ \] ^_
41 : : //; `a bc de fg hi jk lm no pq rs tu vw xy z{ |} ~<del>
42 : : 0x0C,0xC0,0x2C,0xC0,0x22,0x14,0x01,0x00,0x00,0x00,0x00,0x02,0x02,0x22,0x88,0x88,//00h
43 : : 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x66,0x66,0x66,0x66,0x66,0x88,0x88,0x88,//20h
44 : : 0x86,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x68,0x88,0x88,//40h
45 : : 0x86,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x68,0x88,0xA8 //60h
46 : : };
47 : :
48 : : #include <iostream>
49 : 1 : void AutoInputProvider(std::u32string& s)
50 : : {
51 : 1 : std::mt19937 rnd;
52 : 24878 : auto random = [&rnd](unsigned size){ return std::uniform_int_distribution<>(0, (size)-1)(rnd); };
53 : :
54 : 1 : unsigned cur_speed = 16;
55 : 1 : int modifier = 5;
56 : 1 : int repeats = 0;
57 : 1 : char32_t prev_key = 0;
58 : 1 : bool had_ctrlk = false;
59 : :
60 : 16 : auto ResetSeed = [&]()
61 : : {
62 : 15 : rnd.seed(rnd.default_seed);
63 : 15 : modifier = 5;
64 : 15 : repeats = 0;
65 : 15 : prev_key = 0;
66 : 15 : had_ctrlk = false;
67 : 16 : };
68 : :
69 : 1 : ResetSeed();
70 : :
71 : : #ifdef TURBOHACK
72 : : bool TimeHackActive = true;
73 : : #endif
74 : :
75 : 3 : auto Do_Delay_Resize = [&](std::pair<unsigned,unsigned> found_delay,
76 : : unsigned fx,unsigned fy,
77 : : unsigned sx,unsigned sy,
78 : : bool resize_first)
79 : : {
80 : 2 : unsigned time = 0;
81 [ - + ]: 2 : if(found_delay.first != ~0u)
82 : : {
83 : 0 : time = found_delay.second * 250; // milliseconds
84 : 0 : s[found_delay.first+1] = 0;
85 : : }
86 : :
87 [ + - ]: 2 : auto [vcw, vch] = ui.GetCellSize();
88 [ + - ]: 2 : auto [ww, wh] = ui.GetWindowSize();
89 [ + - + + : 2 : if(time == 0 && fx==vcw && fy==vch && sx==ww && sy==wh)
- + - - -
- ]
90 : : {
91 : : // Nothing to do
92 : 0 : return;
93 : : }
94 : :
95 : 2 : unsigned step = 13; // 240fps = 4.166 ms per frame, 120fps = 8.3 ms per frame
96 [ - + + - ]: 2 : unsigned eat_time = std::max(std::min(time, 800u), 200u);
97 : :
98 [ - + ]: 2 : fprintf(stderr, "Resizing - combined with delay %c%u ms (using %u ms for resize)\n",
99 : : resize_first ? '+' : '-',
100 : : time, eat_time);
101 : :
102 : : #ifndef TURBOHACK
103 [ - + ]: 2 : if(!resize_first)
104 : : {
105 : 0 : unsigned eat = (eat_time/step)*step;
106 [ # # ]: 0 : if(eat > time) { time -= eat;
107 [ # # ]: 0 : data.emplace_back(std::string(""));
108 : 0 : SleepFor(time/1e3); } else time = 0;
109 : : }
110 [ - + + + ]: 34 : for(unsigned g=step, t=0; t<eat_time; t+=g, time = time>g ? time-g : 0)
111 : : {
112 : 32 : double factor = t*1.0/eat_time;
113 : 32 : unsigned do_fx = vcw + int(fx-vcw)*factor;
114 : 32 : unsigned do_fy = vch + int(fy-vch)*factor;
115 : 32 : unsigned do_sx = ww + int(sx-ww)*factor;
116 : 32 : unsigned do_sy = wh + int(sy-wh)*factor;
117 : 32 : if(1)
118 : : {
119 : 32 : std::lock_guard<std::mutex> lk(lock);
120 [ + - + - : 64 : data.push_back(std::array{do_fx,do_fy,do_sx,do_sy});
- - ]
121 : 0 : }
122 : 32 : SleepFor(g/1e3);
123 : : }
124 : : #endif
125 : : // Instant
126 : 2 : if(true)
127 : : {
128 : 2 : std::lock_guard<std::mutex> lk(lock);
129 [ + - + - : 4 : data.push_back(std::array{fx,fy,sx,sy});
- - ]
130 : 0 : }
131 [ + - ]: 2 : data.emplace_back(std::string(""));
132 [ - + ]: 2 : if(time > 0)
133 : : {
134 : : #ifdef TURBOHACK
135 : : if(TimeHackActive) time = std::max(150u,std::min(200u, time / 20)); //HACK
136 : : #endif
137 : 0 : SleepFor(time/1e3);
138 : : }
139 : 2 : ResetSeed();
140 : 1 : };
141 : :
142 [ + + - + ]: 8593 : for(std::size_t pos = 0; pos < s.size() && !terminate; ++pos)
143 : : {
144 : : #ifdef TURBOHACK
145 : : if(GetTime() >= 2*60+40) TimeHackActive = false;
146 : : #endif
147 : :
148 : : //std::cerr << s[pos] << '\n';
149 [ + + + + ]: 8592 : switch(s[pos])
150 : : {
151 : 2 : case U'\U00007FFD':
152 : 2 : {
153 : 2 : unsigned font = s[++pos];
154 : 2 : unsigned size = s[++pos];
155 : :
156 : 2 : unsigned fx = font%32, fy = font/32;
157 : 2 : unsigned sx = size%1024, sy = size/1024;
158 : :
159 : : /* Check if there is delay soon hereafter */
160 : 2 : std::pair<unsigned, unsigned> found_delay{ ~0u, 0 };
161 : :
162 [ + - ]: 2 : for(unsigned find = pos+1; find < s.size(); ++find)
163 : : {
164 [ - + ]: 2 : if(s[find] == U'\U00007FFE')
165 : : {
166 : 0 : found_delay = {find, s[find+1]};
167 : 0 : break;
168 : : }
169 [ - + ]: 2 : if(s[find] == U'') continue; // ok
170 [ - + ]: 2 : if(s[find] == U'') continue; // ok
171 [ - + - - ]: 2 : if(s[find] == U'' && s[find+1] == 'd') { ++find;
172 [ # # ]: 0 : if(s[find+1]=='\r') ++find;
173 : 0 : continue; } // ok
174 [ + + - + ]: 2 : if(s[find] == U'\033' && s[find+1]=='f') { ++find; continue; }
175 : : break;
176 : : }
177 : :
178 : 2 : Do_Delay_Resize(found_delay, fx,fy,sx,sy, true);
179 : 2 : break;
180 : : }
181 : 3 : case U'\U00007FFE':
182 : 3 : {
183 [ + - ]: 3 : unsigned delay = s[++pos] * 250; // milliseconds
184 [ + - ]: 3 : if(!delay) break;
185 : :
186 : : /* Check if there is resize soon hereafter */
187 : 3 : std::pair<unsigned, unsigned> found_resize{ ~0u, 0 };
188 : 3 : std::pair<unsigned, unsigned> resize_type{};
189 : :
190 [ + + ]: 3 : for(unsigned find = pos+1; find < s.size(); ++find)
191 : : {
192 [ - + ]: 2 : if(s[find] == U'\U00007FFD')
193 : : {
194 : 0 : resize_type = {s[find+1], s[find+2]};
195 : 0 : found_resize = {pos-1, delay/250};
196 : 0 : break;
197 : : }
198 [ - + ]: 2 : if(s[find] == U'') continue; // ok
199 [ - + ]: 2 : if(s[find] == U'') continue; // ok
200 [ - + - - ]: 2 : if(s[find] == U'' && s[find+1] == 'd') { ++find;
201 [ # # ]: 0 : if(s[find+1]=='\r') ++find;
202 : 0 : continue; } // ok
203 [ - + - - ]: 2 : if(s[find] == U'\033' && s[find+1]=='f') { ++find; continue; }
204 : : break;
205 : : }
206 : :
207 [ - + ]: 3 : if(found_resize.first != ~0u)
208 : : {
209 : 0 : auto[font,size] = resize_type;
210 : 0 : unsigned fx = font%32, fy = font/32;
211 : 0 : unsigned sx = size%1024, sy = size/1024;
212 : 0 : Do_Delay_Resize(found_resize, fx,fy,sx,sy, false);
213 : : }
214 : : else
215 : : {
216 : : //fprintf(stderr, "Performing delay: %u ms\n", delay);
217 : 3 : if(delay)
218 : : {
219 : : #ifdef TURBOHACK
220 : : if(TimeHackActive) delay = std::max(150u,std::min(200u, delay / 20)); //HACK
221 : : #endif
222 : 3 : SleepFor(delay/1e3);
223 : : }
224 : 3 : ResetSeed();
225 : : }
226 : : break;
227 : : }
228 : 34 : case U'\U00007FFF':
229 : 34 : {
230 : 34 : cur_speed = s[++pos];
231 : : //fprintf(stderr, "Input speed changed to %u\n", cur_speed);
232 : 34 : break;
233 : : }
234 : 8553 : default:
235 : 8553 : {
236 : 8553 : bool allow_merge = false;
237 : 8553 : unsigned use_speed = cur_speed;
238 : 8553 : unsigned category = 0; // special key
239 [ + - ]: 8553 : if(s[pos] == U'\003'
240 [ + - + + : 8553 : || (s[pos] == U'x' && had_ctrlk))
+ + ]
241 : : {
242 : : // Reset seed always when ^C is pressed
243 : 9 : ResetSeed();
244 : : }
245 [ + + ]: 8553 : if(s[pos] < 0x80)
246 [ + + ]: 12376 : category = (Delay_CharClassTable[s[pos]/2] >> (4-4*(s[pos]%2))) & 15;
247 : 8553 : unsigned factor = 20;
248 [ + + ]: 8553 : if(s[pos] == prev_key)
249 : : {
250 [ + + ]: 830 : if(repeats >= 2) factor = 15; else { factor = 70; ++repeats; }
251 : : }
252 [ + + + + : 7723 : else switch(repeats = 0, category / 2)
+ - + - ]
253 : : {
254 : 583 : case 0: // 0 special key
255 : 583 : factor = 70;
256 : 583 : break;
257 : 382 : case 1: // 2 fast ctrl
258 : 382 : factor = 13;
259 [ + + ]: 382 : if(s[pos] == U'\033')
260 : : {
261 : 124 : had_ctrlk = true;
262 : : }
263 : : break;
264 : 374 : case 2: // 4 ctrl k
265 : 374 : factor = 35 + random(10);
266 : 374 : had_ctrlk = true;
267 : 374 : break;
268 : 4408 : case 3: // 6 alphanumeric
269 [ + + + + ]: 4483 : modifier = std::min(std::max(modifier + int(random(3)-1) * int(random(3)-1), 1), 18);
270 : 4408 : factor = (random(modifier*5) + 20 + random(46) + random(93)) / 3;
271 : 4408 : allow_merge = true;
272 [ + + ]: 4408 : if(had_ctrlk)
273 : : {
274 : 488 : factor = 35 + random(10);
275 : 488 : had_ctrlk = false;
276 : : }
277 : : break;
278 : 1482 : case 4: // 8 slow key
279 : 1482 : factor = 1000 / (10 + random(51));
280 : 1482 : allow_merge = true;
281 : 1482 : break;
282 : 0 : case 5: // A pondering
283 : 0 : factor = 224;
284 : 0 : break;
285 : 494 : case 6: // C arrow
286 : : // TODO: If previous key was ctrl-Z, give factor=20
287 : 494 : factor = 1000 / (8 + random(66)); // max: 125ms, min: 13ms
288 : 494 : break;
289 : : }
290 : : #ifdef TURBOHACK
291 : : unsigned u=use_speed;
292 : : #define use_speed u
293 : : if(TimeHackActive) use_speed = std::min(2u, use_speed);
294 : : #endif
295 : 8553 : unsigned delay = factor * use_speed / 16;
296 [ + + ]: 8553 : if(use_speed == 1) delay /= 50;
297 : :
298 : 8553 : SleepFor(delay/1e3);
299 : 8553 : prev_key = s[pos];
300 [ + - ]: 17106 : std::string str = ToUTF8(std::u32string(1, s[pos]));
301 : :
302 : 8553 : {
303 [ + - ]: 8553 : std::lock_guard<std::mutex> lk(lock);
304 [ + + + + : 8553 : if(allow_merge && !data.empty() && std::holds_alternative<std::string>(data.back()))
- + ]
305 [ + - + - : 13438 : std::get<std::string>(data.back()) += std::move(str);
+ - ]
306 : : else
307 [ + - ]: 3668 : data.emplace_back(std::move(str));
308 : 0 : }
309 : :
310 : 8553 : break;
311 : : }
312 : : }
313 : : }
314 : 1 : }
315 : :
316 : 39050 : AutoInputResponse GetAutoInput()
317 : : {
318 : 39050 : std::lock_guard<std::mutex> lk(lock);
319 [ + + ]: 39050 : if(data.empty())
320 : : {
321 : 35349 : return std::string{};
322 : : }
323 : 3701 : auto result = std::move(data.front());
324 : 3701 : data.pop_front();
325 : 3701 : return result;
326 [ + - ]: 39050 : }
327 : :
328 : : #include <fstream>
329 : :
330 : 1 : void AutoInputStart(std::string_view filename)
331 : : {
332 [ + - ]: 1 : std::string s;
333 : 1 : try {
334 [ + - + - ]: 1 : std::ifstream t(std::string(filename), std::ios::binary);
335 [ + - ]: 1 : t.seekg(0, std::ios::end);
336 [ + - + - ]: 1 : s.reserve(t.tellg());
337 [ + - ]: 1 : t.seekg(0, std::ios::beg);
338 [ + - ]: 1 : s.assign( std::istreambuf_iterator<char>(t),
339 : : std::istreambuf_iterator<char>() );
340 : 1 : }
341 : 0 : catch(...)
342 : : {
343 [ - - ]: 0 : }
344 [ + - ]: 1 : if(!s.empty())
345 : : {
346 : 1 : finished = false;
347 : : //s = "rm -f 'gruu.cc' '.#gruu.cc'\nstrace -otmptmp joe -tab 4 gruu.cc\nu" + s;
348 [ + - ]: 1 : std::thread t([](std::u32string data){
349 [ + - ]: 1 : AutoInputProvider(data);
350 : 1 : finished = true;
351 [ + - + - ]: 1 : }, FromUTF8(s));
352 [ + - ]: 1 : t.detach();
353 : 1 : }
354 : 1 : }
355 : 1 : void AutoInputEnd()
356 : : {
357 : 1 : terminate = true;
358 : 1 : }
359 : :
360 : 39052 : bool AutoInputActive()
361 : : {
362 : 39052 : return finished == false;
363 : : }
364 : :
365 : : #ifdef RUN_TESTS
366 : 3 : TEST(AutoInput, active_should_be_false)
367 : : {
368 : 1 : EXPECT_FALSE(AutoInputActive());
369 : 1 : }
370 : 3 : TEST(AutoInput, works)
371 : : {
372 : 1 : SetTimeFactor(0.);
373 : 1 : AutoInputStart("test/inputter.dat");
374 : 1 : unsigned num_events = 0;
375 : 1 : AdvanceTime(3600);
376 [ + + ]: 39051 : while(AutoInputActive())
377 : : {
378 : 39050 : auto resp = GetAutoInput();
379 : 39050 : ++num_events;
380 : 39050 : }
381 : 1 : EXPECT_GE(num_events, 10000u);
382 : 1 : EXPECT_LT(num_events, 150000u);
383 : 1 : AutoInputEnd();
384 : 1 : terminate = false;
385 : 1 : }
386 : : #endif
|