class WACOM_POINTER create {XINPUT_IVY} make, make_self_test feature {} make(a_xinput_ivy: XINPUT_IVY) is require a_xinput_ivy /= Void do xinput_ivy := a_xinput_ivy prediction_time := 25 end make_self_test is local angle: REAL x, y: REAL mc: MATH_CONSTANTS i: INTEGER do from angle := 80 prediction_time := 10 until i > 36 loop x := (angle * mc.Pi / 180).cos * 1000 y := (angle * mc.Pi / 180).sin * 1000 if i > 2 then --io.put_string(once "Real position: x=") io.put_real(x) --io.put_string(once ", y=") io.put_character(' ') io.put_real(y) io.put_new_line end update_predicted_position(x, y, angle) if i >= 2 then --io.put_string(once "Predicted position: x=") io.put_real(predicted_x) --io.put_string(once ", y=") io.put_character(' ') io.put_real(predicted_y) io.put_character(' ') --io.put_new_line end angle := angle + prediction_time i := i + 1 end end xinput_ivy: XINPUT_IVY x_offset, y_offset: INTEGER cropping: BOOLEAN x_min_crop, y_min_crop, x_max_crop, y_max_crop, crop_width, crop_height: INTEGER prediction_time: REAL ignore_rate, ignored_counter: INTEGER pointer_message_header, button_message_header: STRING old_presure: INTEGER type_to_name(type_id: INTEGER): STRING is do inspect type_id when 2066 then Result := once "pen_2066" -- 0x812 when 2049 then Result := once "pen_2049" -- 0x801 -- 0x012 when 2082 then Result := once "pen_2082" -- 0x822 -- 0x842 -- 0x852 when 2083 then Result := once "grip_pen_2083" -- 0x823 when 2067 then Result := once "classic_pen_2067" -- 0x813 when 2181 then Result := once "marker_pen_2181" -- 0x885 -- 0x022 when 2098 then Result := once "stroke_pen_2098" -- 0x832 -- 0x032 when 3346 then Result := once "airbrush_3346" -- 0xd12 -- 0x912 -- 0x112 when 2323 then Result := once "airbrush_2323" -- 0x913 when 2090 then Result := once "eraser_2090" -- 0x82a -- 0x85a -- 0x91a -- 0xd1a -- 0x0fa when 2091 then Result := once "grip_pen_eraser_2091" -- 0x82b when 2075 then Result := once "classic_pen_eraser_2075" -- 0x81b when 2331 then Result := once "airbrush_eraser_2331" -- 0x91b else Result := once "" Result.clear_count type_id.append_in(Result) end end feature {} reconfigure_crop(parameters: FAST_ARRAY[STRING]) is -- parameters.item(0): crop configuration require parameters.count = 1 do if is_valid_crop(parameters.item(0)) then set_crop(parameters.item(0)) else io.put_string(once "Recieved CHANGE_CROP message with % %invalid crop definition: ") io.put_line(parameters.item(0)) end end feature {XINPUT_IVY} set_x_offset(offset: INTEGER) is do x_offset := offset end set_y_offset(offset: INTEGER) is do y_offset := offset end is_valid_crop(description: STRING): BOOLEAN is require description /= Void local tmp: STRING values: ARRAY[STRING] do Result := description.occurrences(',') = 3 if Result then tmp := description.twin tmp.replace_all(',', ' ') from values := tmp.split Result := values.count = 4 until not Result or else values.is_empty loop Result := values.last.is_integer values.remove_last end end end set_crop(description: STRING) is require is_valid_crop(description) local values: ARRAY[STRING] do description.replace_all(',', ' ') values := description.split x_min_crop := values.item(1).to_integer y_min_crop := values.item(2).to_integer x_max_crop := values.item(3).to_integer y_max_crop := values.item(4).to_integer cropping := True crop_width := x_max_crop - x_min_crop crop_height := y_max_crop - y_min_crop end set_prediction (milliseconds: REAL) is do prediction_time := milliseconds end set_ignore_rate (n: INTEGER) is do ignore_rate := n end set_message_header (header: STRING) is local history_size: INTEGER do pointer_message_header := "pointer" pointer_message_header.append(header) button_message_header := "button" button_message_header.append(header) history_size := 1 create x_history.make(0, history_size) create y_history.make(0, history_size) create time_history.make(0, history_size) end crop_identifier (id: STRING) is local msg: STRING do msg := once "" msg.clear_count msg.append(once "^CHANGE_CROP device_id=") msg.append(id) msg.append(once " crop=(.*)") xinput_ivy.ivy.subscribe(msg, agent reconfigure_crop) end device_to_screen_x (x: INTEGER): INTEGER is do Result := ((x - 150) / 86650 * 1600 + x_offset).force_to_integer_32 -- **** geometrie paramétrable end device_to_screen_y(y: INTEGER): INTEGER is do Result := ((y - 300) / 64700 * 1200 + y_offset).force_to_integer_32 -- **** geometrie paramétrable end x_history, y_history, time_history: RING_ARRAY[INTEGER] mean(history: RING_ARRAY[INTEGER]): REAL is require history.count > 0 local sum: INTEGER_64 i: INTEGER do from i := history.lower until i > history.upper loop sum := sum + history.item(i) i := i + 1 end Result := sum / history.count end move (pointer: X_INPUT_DEVICE) is local x, y, presure: INTEGER t3: REAL_64 message: STRING do x := pointer.motion_axis_data(1) y := pointer.motion_axis_data(2) --update_predicted_position(x, y, pointer.event_time) x_history.remove_first x_history.add_last(x) y_history.remove_first y_history.add_last(y) time_history.remove_first time_history.add_last(pointer.event_time) t3 := mean(time_history) if t3 /= t2 then update_predicted_position(mean(x_history), mean(y_history), t3) end presure := pointer.motion_axis_data(3) if cropping then if x < x_min_crop or else y < y_min_crop or else x > x_max_crop or else y > y_max_crop then ignored_counter := -1 end end if ignored_counter < ignore_rate and then ((old_presure = 0) = (presure = 0)) then ignored_counter := ignored_counter + 1 else ignored_counter := 0 message := once "" message.copy(pointer_message_header) print_data_in(message, x, y, presure, pointer.motion_axis_data(4), pointer.motion_axis_data(5), pointer.motion_axis_data(6), pointer.event_time, once "unchanged") xinput_ivy.ivy.send_message(message) end old_presure := presure end print_data_in(message: STRING; x, y, presure, v4, v5, v6, time: INTEGER; proximity_description: STRING) is do message.append(once " x=") device_to_screen_x(x).append_in(message) message.append(once " y=") device_to_screen_y(y).append_in(message) message.append(once " presure=") presure.append_in(message) message.append(once " tilt_x=") v4.low_16.append_in(message) message.append(once " tilt_y=") v5.low_16.append_in(message) message.append(once " wheel=") v6.low_16.append_in(message) message.append(once " predicted_x=") device_to_screen_x(predicted_x).append_in(message) message.append(once " predicted_y=") device_to_screen_y(predicted_y).append_in(message) message.append(once " type=") message.append(type_to_name(v4.high_16)) message.append(once " serial_number=") (v5.high_16.to_integer_32 |<< 16).bit_or((0x0000FFFF).bit_and(v6.high_16)).append_in(message) message.append(once " time=") time.append_in(message) message.append(once " hires_x=") if not cropping then (x/86400).append_in(message) -- **** geometrie paramétrable else ((x - x_min_crop) / crop_width).append_in(message) end message.append(once " hires_y=") if not cropping then (y/65000).append_in(message) -- **** geometrie paramétrable else ((y - y_min_crop) / crop_height).append_in(message) end message.append(once " proximity=") message.append(proximity_description) end button(pressed: BOOLEAN; pointer: X_INPUT_DEVICE) is local x, y, presure: INTEGER t3: REAL_64 message: STRING do x := pointer.motion_axis_data(1) y := pointer.motion_axis_data(2) --update_predicted_position(x, y, pointer.event_time) x_history.remove_first x_history.add_last(x) y_history.remove_first y_history.add_last(y) time_history.remove_first time_history.add_last(pointer.event_time) t3 := mean(time_history) if t3 /= t2 then update_predicted_position(mean(x_history), mean(y_history), t3) end message := once "" message.copy(button_message_header) message.append(once " button=") pointer.button_number.append_in(message) message.append(once " status=") if pressed then message.append(once "down") else message.append(once "up") end presure := pointer.motion_axis_data(3) print_data_in(message, x, y, presure, pointer.motion_axis_data(4), pointer.motion_axis_data(5), pointer.motion_axis_data(6), pointer.event_time, once "unchanged") xinput_ivy.ivy.send_message(message) end proximity(in: BOOLEAN; pointer: X_INPUT_DEVICE) is local x, y: INTEGER t3: REAL_64 message, proximity_status: STRING do x := pointer.proximity_axis_data(1) y := pointer.proximity_axis_data(2) --update_predicted_position(x, y, pointer.event_time) --*** exploiter le cas proximity in=True pour vider l'historique x_history.remove_first x_history.add_last(x) y_history.remove_first y_history.add_last(y) time_history.remove_first time_history.add_last(pointer.event_time) t3 := mean(time_history) if t3 /= t2 then update_predicted_position(mean(x_history), mean(y_history), t3) end if in then proximity_status := once "In" else proximity_status := once "Out" end message := once "" message.copy(pointer_message_header) print_data_in(message, x, y, pointer.proximity_axis_data(3), pointer.proximity_axis_data(4), pointer.proximity_axis_data(5), pointer.proximity_axis_data(6), pointer.event_time, proximity_status) xinput_ivy.ivy.send_message(message) end predicted_x, predicted_y: INTEGER int_update_predicted_position(x3, y3, t3: INTEGER) is local dt: REAL -- delta time, temporary value s3x, s3y, s3: REAL -- speed beetween P2 and P3 s3xn, s3yn: REAL -- normalized speed between point P2 and P3 a3x, a3y: REAL -- acceleration at point P2 (needs P3!) a3xm, a3ym: REAL -- a3 value in mobile coordinates (s2 vector) a4x, a4y: REAL -- a3 value rotated by (s2 vector, s3 vector) angle do dt := t3 - t2 --s3x := (x3 - x2).to_real_64 / dt --s3y := (y3 - y2).to_real_64 / dt s3 := (s3x * s3x + s3y * s3y).sqrt s3xn := s3x / s3 s3yn := s3y / s3 dt := (t3 - t1) / 2 a3x := (s3x - s2x) / dt a3y := (s3y - s2y) / dt a3xm := a3x * s2xn + a3y * s2yn a3ym := -a3x * s2yn + a3y * s2xn a4x := s3xn * a3xm - s3yn * a3ym a4y := s3yn * a3xm + s3xn * a3ym dt := (t3 - t2 + prediction_time) / 2 predicted_x := x3 + (prediction_time * (s3x + a4x * dt)).force_to_integer_32 predicted_y := y3 + (prediction_time * (s3y + a4y * dt)).force_to_integer_32 if (prediction_time * (s3y + a4y * dt)).abs > 5300 then io.put_string(once "x2=" + x2.to_string + once " y2=" + y2.to_string) io.put_string(once " x3=" + x3.to_string + once " y3=" + y3.to_string) io.put_string(once " s3x=" + s3x.to_string + once " s3y=" + s3y.to_string) io.put_string(once " a3x=" + a3x.to_string + once " a3y=" + a3y.to_string) io.put_string(once " a3xm=" + a3xm.to_string + once " a3ym=" + a3ym.to_string) io.put_string(once " a4x=" + a4x.to_string + once " a4y=" + a4y.to_string) io.put_string(once " dt=" + dt.to_string) io.put_string(once " ****") io.put_new_line end -- Shift values x2 := x3; y2 := y3 s2x := s3x; s2y := s3y s2xn := s3xn; s2yn := s3yn t1 := t2; t2 := t3 end update_predicted_position(x3, y3, t3: REAL) is require t2 /= t3 local dt: REAL -- delta time, temporary value s3x, s3y, s3: REAL -- speed beetween P2 and P3 s3xn, s3yn: REAL -- normalized speed between point P2 and P3 a3x, a3y: REAL -- acceleration at point P2 (needs P3!) a3xm, a3ym: REAL -- a3 value in mobile coordinates (s2 vector) a4x, a4y: REAL -- a3 value rotated by (s2 vector, s3 vector) angle do dt := t3 - t2 s3x := (x3 - x2) / dt s3y := (y3 - y2) / dt s3 := (s3x * s3x + s3y * s3y).sqrt if s3 /= 0 then s3xn := s3x / s3 s3yn := s3y / s3 end dt := (t3 - t1) / 2 a3x := (s3x - s2x) / dt a3y := (s3y - s2y) / dt a3xm := a3x * s2xn + a3y * s2yn a3ym := -a3x * s2yn + a3y * s2xn a4x := s3xn * a3xm - s3yn * a3ym a4y := s3yn * a3xm + s3xn * a3ym --dt := (t3 - t2 + prediction_time) / 2 -- acceleration is constant -- predicted_x := (x3 + prediction_time * (s3x + a3x * dt)).force_to_integer_32 -- predicted_y := (y3 + prediction_time * (s3y + a3y * dt)).force_to_integer_32 -- acceleration is constant in mobile coordinates -- predicted_x := (x3 + prediction_time * (s3x + a4x * dt)).force_to_integer_32 -- predicted_y := (y3 + prediction_time * (s3y + a4y * dt)).force_to_integer_32 -- acceleration variation is constant -- a4x := a3x + (a3x - a2x) * (prediction_time + t2 - t1) / (t3 + t1 - t2 - t0) -- a4y := a3y + (a3y - a2y) * (prediction_time + t2 - t1) / (t3 + t1 - t2 - t0) -- predicted_x := (x3 + prediction_time * (s3x + a4x * dt)).force_to_integer_32 -- predicted_y := (y3 + prediction_time * (s3y + a4y * dt)).force_to_integer_32 -- half constant in mobile coordinates, half constant variation dt := (prediction_time + t2 - t1) / (t3 + t1 - t2 - t0) a4x := (a4x + a3x + (a3x - a2x) * dt) / 2 a4y := (a4y + a3y + (a3y - a2y) * dt) / 2 dt := (t3 - t2 + prediction_time) / 2 predicted_x := (x3 + prediction_time * (s3x + a4x * dt)).force_to_integer_32 predicted_y := (y3 + prediction_time * (s3y + a4y * dt)).force_to_integer_32 -- Shift values x2 := x3; y2 := y3 s2x := s3x; s2y := s3y s2xn := s3xn; s2yn := s3yn a2x := a3x; a2y := a3y t0 := t1; t1 := t2; t2 := t3 end a2x, a2y: REAL t0: REAL --x2, y2: INTEGER -- point P2 coordinates x2, y2: REAL -- point P2 coordinates s2x, s2y: REAL -- speed beetween P1 and P2 s2xn, s2yn: REAL -- normalized speed between P1 and P2 --t1, t2: INTEGER -- timestamp for points P1 and P2 t1, t2: REAL -- timestamp for points P1 and P2 end