procon_bypass_man 0.1.22 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +1 -1
  3. data/.github/workflows/gitleacks.yml +12 -0
  4. data/.github/workflows/release.yml +1 -0
  5. data/.github/workflows/ruby.yml +1 -1
  6. data/CHANGELOG.md +22 -0
  7. data/Gemfile.lock +5 -5
  8. data/docs/getting_started.md +9 -1
  9. data/docs/setup_raspi.md +0 -1
  10. data/lib/procon_bypass_man/buttons_setting_configuration/loader.rb +1 -1
  11. data/lib/procon_bypass_man/{commands → bypass}/bypass_command.rb +36 -24
  12. data/lib/procon_bypass_man/bypass/usb_hid_logger.rb +3 -0
  13. data/lib/procon_bypass_man/bypass.rb +40 -23
  14. data/lib/procon_bypass_man/commands/print_boot_message_command.rb +2 -3
  15. data/lib/procon_bypass_man/commands/send_error_command.rb +2 -2
  16. data/lib/procon_bypass_man/commands.rb +0 -3
  17. data/lib/procon_bypass_man/configuration.rb +22 -3
  18. data/lib/procon_bypass_man/device_connection/command.rb +28 -0
  19. data/lib/procon_bypass_man/device_connection/executor.rb +190 -0
  20. data/lib/procon_bypass_man/device_connection/output_report_generator.rb +42 -0
  21. data/lib/procon_bypass_man/device_connection/output_report_markerable.rb +28 -0
  22. data/lib/procon_bypass_man/device_connection/output_report_sub_command_table.rb +133 -0
  23. data/lib/procon_bypass_man/device_connection/output_report_watcher.rb +41 -0
  24. data/lib/procon_bypass_man/device_connection/pre_bypass.rb +67 -0
  25. data/lib/procon_bypass_man/device_connection/procon_setting_overrider.rb +78 -0
  26. data/lib/procon_bypass_man/device_connection/spoofing_output_report_watcher.rb +39 -0
  27. data/lib/procon_bypass_man/device_connection.rb +16 -0
  28. data/lib/procon_bypass_man/device_model.rb +17 -0
  29. data/lib/procon_bypass_man/io_monitor.rb +20 -1
  30. data/lib/procon_bypass_man/procon/macro_builder.rb +5 -3
  31. data/lib/procon_bypass_man/procon/suppress_rumble.rb +13 -0
  32. data/lib/procon_bypass_man/{domains → procon/value_objects}/analog_stick_position.rb +0 -0
  33. data/lib/procon_bypass_man/{domains → procon/value_objects}/binary/base.rb +0 -0
  34. data/lib/procon_bypass_man/{domains → procon/value_objects}/binary/has_immutable_binary.rb +0 -0
  35. data/lib/procon_bypass_man/{domains → procon/value_objects}/binary/has_mutable_binary.rb +0 -0
  36. data/lib/procon_bypass_man/{domains → procon/value_objects}/binary/inbound_procon_binary.rb +5 -0
  37. data/lib/procon_bypass_man/{domains → procon/value_objects}/binary/processing_procon_binary.rb +0 -0
  38. data/lib/procon_bypass_man/procon/value_objects/binary.rb +11 -0
  39. data/lib/procon_bypass_man/{domains → procon/value_objects}/bypass_mode.rb +0 -0
  40. data/lib/procon_bypass_man/procon/value_objects/rumble_binary.rb +18 -0
  41. data/lib/procon_bypass_man/procon.rb +8 -0
  42. data/lib/procon_bypass_man/procon_display/http_request.rb +31 -0
  43. data/lib/procon_bypass_man/procon_display/http_response.rb +23 -0
  44. data/lib/procon_bypass_man/procon_display/server.rb +33 -0
  45. data/lib/procon_bypass_man/procon_display/server_app.rb +17 -0
  46. data/lib/procon_bypass_man/procon_display/status.rb +20 -0
  47. data/lib/procon_bypass_man/procon_display.rb +10 -0
  48. data/lib/procon_bypass_man/{commands → remote_pbm_action/commands}/run_remote_pbm_action_dispatch_command.rb +0 -0
  49. data/lib/procon_bypass_man/remote_pbm_action/restore_pbm_setting.rb +7 -1
  50. data/lib/procon_bypass_man/remote_pbm_action.rb +1 -0
  51. data/lib/procon_bypass_man/runner.rb +10 -13
  52. data/lib/procon_bypass_man/support/cycle_sleep.rb +22 -0
  53. data/lib/procon_bypass_man/support/device_mouse_finder.rb +60 -0
  54. data/lib/procon_bypass_man/{device_procon_finder.rb → support/device_procon_finder.rb} +0 -0
  55. data/lib/procon_bypass_man/support/never_exit_accidentally.rb +3 -3
  56. data/lib/procon_bypass_man/support/safe_timeout.rb +7 -1
  57. data/lib/procon_bypass_man/{usb_device_controller.rb → support/usb_device_controller.rb} +9 -4
  58. data/lib/procon_bypass_man/support/yaml_loader.rb +12 -0
  59. data/lib/procon_bypass_man/version.rb +1 -1
  60. data/lib/procon_bypass_man/websocket/client.rb +1 -4
  61. data/lib/procon_bypass_man.rb +66 -51
  62. data/project_template/app.rb +23 -7
  63. data/sig/main.rbs +4 -16
  64. metadata +37 -16
  65. data/lib/procon_bypass_man/commands/connect_device_command.rb +0 -18
  66. data/lib/procon_bypass_man/device_connector.rb +0 -293
  67. data/lib/procon_bypass_man/domains.rb +0 -13
@@ -5,9 +5,11 @@ class ProconBypassMan::Procon::MacroBuilder
5
5
  return subjects.first.to_steps
6
6
  end
7
7
 
8
- base = subjects.first
9
- remain = subjects[1..-1]
10
- remain.map { |x| base.to_steps.zip(x.to_steps) }.first
8
+ subjects.inject([[], []]) do |acc, item|
9
+ acc[0] << item.to_steps[0]
10
+ acc[1] << item.to_steps[1]
11
+ acc
12
+ end
11
13
  end
12
14
  end
13
15
 
@@ -0,0 +1,13 @@
1
+ class ProconBypassMan::SuppressRumble
2
+ # @param [String] binary
3
+ def initialize(binary: )
4
+ @binary = binary
5
+ end
6
+
7
+ # @return [String]
8
+ def execute
9
+ new_raw = ["100c0001404000014040"].pack("H*")
10
+ new_raw[1] = @binary[1]
11
+ new_raw
12
+ end
13
+ end
@@ -6,4 +6,9 @@ class ProconBypassMan::Domains::InboundProconBinary < ProconBypassMan::Domains::
6
6
  def user_operation_data?
7
7
  binary[0] == "\x30".b
8
8
  end
9
+
10
+ # @return [Boolean]
11
+ def rumble_data?
12
+ binary[0] == "\x10".b
13
+ end
9
14
  end
@@ -0,0 +1,11 @@
1
+ module ProconBypassMan
2
+ module Domains
3
+ module Binary; end
4
+ end
5
+ end
6
+
7
+ require "procon_bypass_man/procon/value_objects/binary/base"
8
+ require "procon_bypass_man/procon/value_objects/binary/has_immutable_binary"
9
+ require "procon_bypass_man/procon/value_objects/binary/has_mutable_binary"
10
+ require "procon_bypass_man/procon/value_objects/binary/inbound_procon_binary"
11
+ require "procon_bypass_man/procon/value_objects/binary/processing_procon_binary"
@@ -0,0 +1,18 @@
1
+ class ProconBypassMan::RumbleBinary
2
+ # @param [String] binary
3
+ def initialize(binary: )
4
+ @binary = binary
5
+ end
6
+
7
+ def unpack
8
+ @binary.unpack("H*")
9
+ end
10
+
11
+ def noop!
12
+ @binary = ProconBypassMan::SuppressRumble.new(binary: @binary).execute
13
+ end
14
+
15
+ def raw
16
+ @binary
17
+ end
18
+ end
@@ -1,6 +1,13 @@
1
1
  require "procon_bypass_man/procon/macro_plugin_map"
2
2
 
3
3
  class ProconBypassMan::Procon
4
+ require "procon_bypass_man/procon/value_objects/analog_stick"
5
+ require "procon_bypass_man/procon/value_objects/analog_stick_position"
6
+ require "procon_bypass_man/procon/value_objects/procon_reader"
7
+ require "procon_bypass_man/procon/value_objects/rumble_binary"
8
+ require "procon_bypass_man/procon/value_objects/binary"
9
+ require "procon_bypass_man/procon/value_objects/bypass_mode"
10
+
4
11
  require "procon_bypass_man/procon/consts"
5
12
  require "procon_bypass_man/procon/mode_registry"
6
13
  require "procon_bypass_man/procon/macro"
@@ -11,6 +18,7 @@ class ProconBypassMan::Procon
11
18
  require "procon_bypass_man/procon/user_operation"
12
19
  require "procon_bypass_man/procon/flip_cache"
13
20
  require "procon_bypass_man/procon/press_button_aware"
21
+ require "procon_bypass_man/procon/suppress_rumble"
14
22
 
15
23
  attr_accessor :user_operation
16
24
 
@@ -0,0 +1,31 @@
1
+ module ProconBypassMan::ProconDisplay
2
+ # NOTE Support GET only
3
+ class HttpRequest
4
+ def self.parse(conn)
5
+ headers = {}
6
+ loop do
7
+ line = conn.gets("\n")&.strip
8
+ break if line.nil? || line.strip.empty?
9
+ key, value = line.split(/:\s/, 2)
10
+ headers[key] = value
11
+ end
12
+
13
+ new(headers)
14
+ end
15
+
16
+ def initialize(headers)
17
+ @headers = headers
18
+ end
19
+
20
+ def path
21
+ request_method_and_path = @headers.detect { |key, _value| key.start_with?("GET") }.first
22
+ if request_method_and_path =~ /(?:GET) ([^ ]+)/ && (path = $1)
23
+ return path
24
+ end
25
+ end
26
+
27
+ def to_hash
28
+ { "PATH" => path }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,23 @@
1
+ module ProconBypassMan::ProconDisplay
2
+ class HttpResponse
3
+ def initialize(body, status: , format: "text/json")
4
+ @body = body&.to_json
5
+ @status = status
6
+ @format = format
7
+ end
8
+
9
+ def to_s
10
+ <<~EOH
11
+ HTTP/1.1 #{@status}
12
+ Content-Length: #{@body&.bytes&.size || 0}
13
+ Content-Type: #{@format}
14
+ Access-Control-Allow-Origin: *
15
+ Access-Control-Allow-Methods: GET
16
+ Access-Control-Allow-Private-Network:true
17
+ Connection: close
18
+
19
+ #{@body}
20
+ EOH
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,33 @@
1
+ require 'socket'
2
+
3
+ module ProconBypassMan::ProconDisplay
4
+ class Server
5
+ PORT = 9900
6
+
7
+ def self.start!
8
+ Thread.new do
9
+ new.start_with_foreground
10
+ end
11
+ end
12
+
13
+ def initialize
14
+ @server = TCPServer.new('0.0.0.0', PORT)
15
+ end
16
+
17
+ def start_with_foreground
18
+ loop do
19
+ conn = @server.accept
20
+ response = ServerApp.new(
21
+ HttpRequest.parse(conn).to_hash
22
+ ).call
23
+ conn.write(response)
24
+ conn.close
25
+ end
26
+ rescue Errno::EADDRINUSE => e
27
+ ProconBypassMan::SendErrorCommand.execute(error: e)
28
+ rescue => e
29
+ ProconBypassMan::SendErrorCommand.execute(error: e)
30
+ retry
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,17 @@
1
+ module ProconBypassMan::ProconDisplay
2
+ class ServerApp
3
+ def initialize(env)
4
+ @env = env
5
+ end
6
+
7
+ def call
8
+ case @env["PATH"]
9
+ when "/input"
10
+ response = ProconBypassMan::ProconDisplay::Status.instance.current
11
+ HttpResponse.new(response, status: 200).to_s
12
+ else
13
+ HttpResponse.new(nil, status: 404).to_s
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ require 'singleton'
2
+
3
+ class ProconBypassMan::ProconDisplay::Status
4
+ include Singleton
5
+
6
+ # @return [Hash]
7
+ def current
8
+ @current || {}
9
+ end
10
+
11
+ # @return [void]
12
+ # @param [Hash] value
13
+ def current=(value)
14
+ if value.is_a?(Hash)
15
+ @current = value
16
+ else
17
+ @current = nil
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,10 @@
1
+ # 入力表示用
2
+
3
+ module ProconBypassMan::ProconDisplay
4
+ end
5
+
6
+ require "procon_bypass_man/procon_display/server"
7
+ require "procon_bypass_man/procon_display/server_app"
8
+ require "procon_bypass_man/procon_display/status"
9
+ require "procon_bypass_man/procon_display/http_response"
10
+ require "procon_bypass_man/procon_display/http_request"
@@ -17,7 +17,8 @@ module ProconBypassMan
17
17
  path: ProconBypassMan::ButtonsSettingConfiguration.instance.setting_path,
18
18
  content: setting,
19
19
  )
20
- ProconBypassMan.hot_reload!
20
+
21
+ hot_reload!
21
22
  end
22
23
 
23
24
  private
@@ -29,6 +30,11 @@ module ProconBypassMan
29
30
  def after_action_callback
30
31
  be_processed
31
32
  end
33
+
34
+ # @return [void]
35
+ def hot_reload!
36
+ Process.kill(:USR2, ProconBypassMan.pid)
37
+ end
32
38
  end
33
39
  end
34
40
  end
@@ -6,6 +6,7 @@ module ProconBypassMan
6
6
  require "procon_bypass_man/remote_pbm_action/stop_pbm_action"
7
7
  require "procon_bypass_man/remote_pbm_action/restore_pbm_setting.rb"
8
8
  require "procon_bypass_man/remote_pbm_action/commands/update_remote_pbm_action_status_command"
9
+ require "procon_bypass_man/remote_pbm_action/commands/run_remote_pbm_action_dispatch_command"
9
10
  require "procon_bypass_man/remote_pbm_action/value_objects/remote_pbm_action_object"
10
11
 
11
12
  ACTION_CHANGE_PBM_VERSION = "change_pbm_version"
@@ -8,13 +8,11 @@ class ProconBypassMan::Runner
8
8
  def initialize(gadget: , procon: )
9
9
  @gadget = gadget
10
10
  @procon = procon
11
-
12
- ProconBypassMan::PrintBootMessageCommand.execute
13
11
  end
14
12
 
15
13
  def run
16
14
  self_read, self_write = IO.pipe
17
- %w(TERM INT USR1 USR2).each do |sig|
15
+ %w(TERM INT USR2).each do |sig|
18
16
  begin
19
17
  trap sig do
20
18
  self_write.puts(sig)
@@ -25,25 +23,26 @@ class ProconBypassMan::Runner
25
23
  end
26
24
 
27
25
  loop do
28
- $will_terminate_token = false
29
26
  # NOTE メインプロセスではThreadをいくつか起動しているので念のためパフォーマンスを優先するためにforkしていく
30
27
  child_pid = Kernel.fork {
28
+ $will_terminate_token = false
31
29
  DRb.start_service if defined?(DRb)
32
30
  ProconBypassMan::RemoteMacroReceiver.start!
33
- ProconBypassMan::BypassCommand.new(gadget: @gadget, procon: @procon).execute
31
+ ProconBypassMan::ProconDisplay::Server.start!
32
+ ProconBypassMan::BypassCommand.new(gadget: @gadget, procon: @procon).execute # ここでblockingする
33
+ next
34
34
  }
35
35
 
36
36
  begin
37
- # TODO 小プロセスが消滅した時に、メインプロセスは生き続けてしまい、何もできなくなる問題がある
37
+ # TODO 子プロセスが消滅した時に、メインプロセスは生き続けてしまい、何もできなくなる問題がある
38
38
  while(readable_io = IO.select([self_read]))
39
39
  signal = readable_io.first[0].gets.strip
40
40
  handle_signal(signal)
41
41
  end
42
42
  rescue InterruptForRestart
43
- $will_terminate_token = true
44
- Process.kill("TERM", child_pid)
43
+ ProconBypassMan::PrintMessageCommand.execute(text: "設定ファイルの再読み込みを開始します")
44
+ Process.kill("USR2", child_pid)
45
45
  Process.wait
46
- ProconBypassMan::PrintMessageCommand.execute(text: "Reloading config file")
47
46
  begin
48
47
  ProconBypassMan::ButtonsSettingConfiguration::Loader.reload_setting
49
48
  ProconBypassMan::SendReloadConfigEventCommand.execute
@@ -53,12 +52,10 @@ class ProconBypassMan::Runner
53
52
  end
54
53
  ProconBypassMan::PrintMessageCommand.execute(text: "バイパス処理を再開します")
55
54
  rescue Interrupt
56
- $will_terminate_token = true
55
+ puts
56
+ ProconBypassMan::PrintMessageCommand.execute(text: "処理を終了します")
57
57
  Process.kill("TERM", child_pid)
58
58
  Process.wait
59
- ProconBypassMan::PrintMessageCommand.execute(text: "処理を終了します")
60
- @gadget&.close
61
- @procon&.close
62
59
  break
63
60
  end
64
61
  end
@@ -0,0 +1,22 @@
1
+ # n秒間sleepしつつ、mainスレッドをm秒間隔で動かしたい時に使う
2
+ class ProconBypassMan::CycleSleep
3
+ attr_accessor :cycle_interval, :execution_cycle
4
+
5
+ def initialize(cycle_interval: , execution_cycle: )
6
+ @cycle_interval = cycle_interval
7
+ @execution_cycle = execution_cycle
8
+ @counter = 0
9
+ end
10
+
11
+ def sleep_or_execute
12
+ result = nil
13
+ if @counter >= @execution_cycle
14
+ @counter = 0
15
+ result = yield
16
+ else
17
+ @counter += 1
18
+ end
19
+ sleep(@cycle_interval)
20
+ return result
21
+ end
22
+ end
@@ -0,0 +1,60 @@
1
+ class ProconBypassMan::DeviceMouseFinder
2
+ class USBDevice < Struct.new(:display_name, :event_no)
3
+ # TODO bInterfaceProtocolの値を見てmouseかを判断したい
4
+ def mouse?
5
+ !!(display_name =~ /mouse/i)
6
+ end
7
+
8
+ def keyboard?
9
+ !!(display_name =~ /keyboard/i)
10
+ end
11
+
12
+ def event_device_path
13
+ "/dev/input/#{event_no}"
14
+ end
15
+ end
16
+
17
+ class Parser
18
+ def self.parse(shell_output)
19
+ instance = new
20
+ instance.set_usb_devices_from(shell_output)
21
+ instance
22
+ end
23
+
24
+ def set_usb_devices_from(shell_output)
25
+ @usb_devices =
26
+ shell_output.split(/\n\n/).map do |text|
27
+ display_name = /N: Name="(.+?)"$/ =~ text && $1
28
+ event_no = /H: Handlers=.*?(event\d).*?$/ =~ text && $1
29
+ USBDevice.new(display_name, event_no)
30
+ end
31
+ end
32
+
33
+ def usb_devices
34
+ @usb_devices ||= []
35
+ end
36
+ end
37
+
38
+ def self.find
39
+ new.find
40
+ end
41
+
42
+ # @return [String, NilClass]
43
+ def find
44
+ find_path
45
+ end
46
+
47
+ private
48
+
49
+ def find_path
50
+ Parser.parse(shell_output).usb_devices.detect(&:mouse?)&.event_device_path
51
+ end
52
+
53
+ def shell_output
54
+ `bash -c '#{shell}'`
55
+ end
56
+
57
+ def shell
58
+ 'cat /proc/bus/input/devices'
59
+ end
60
+ end
@@ -1,11 +1,11 @@
1
1
  module ProconBypassMan
2
2
  module NeverExitAccidentally
3
- def exit_if_allow(status)
3
+ def self.exit_if_allow_at_config
4
4
  if ProconBypassMan.never_exit_accidentally
5
- eternal_sleep
5
+ ProconBypassMan.eternal_sleep
6
6
  else
7
7
  yield if block_given?
8
- exit(status)
8
+ exit 1
9
9
  end
10
10
  end
11
11
  end
@@ -7,8 +7,14 @@ module ProconBypassMan
7
7
  @timeout = timeout
8
8
  end
9
9
 
10
+ # @raise [Timeout]
10
11
  def throw_if_timeout!
11
- raise Timeout if @timeout < Time.now
12
+ raise Timeout if timeout?
13
+ end
14
+
15
+ # @return [Boolean]
16
+ def timeout?
17
+ @timeout < Time.now
12
18
  end
13
19
  end
14
20
  end
@@ -1,9 +1,13 @@
1
1
  class ProconBypassMan::UsbDeviceController
2
2
  class << self
3
- def reset
4
- system('echo > /sys/kernel/config/usb_gadget/procon/UDC')
5
- system('ls /sys/class/udc > /sys/kernel/config/usb_gadget/procon/UDC')
6
- sleep 0.5
3
+ def reset(cooldown: 0.5)
4
+ [ "echo > /sys/kernel/config/usb_gadget/procon/UDC",
5
+ "ls /sys/class/udc > /sys/kernel/config/usb_gadget/procon/UDC",
6
+ ].each do |shell|
7
+ system shell
8
+ ProconBypassMan.logger.debug { "[SHELL] #{shell}" }
9
+ end
10
+ sleep cooldown
7
11
  end
8
12
 
9
13
  def init
@@ -42,6 +46,7 @@ class ProconBypassMan::UsbDeviceController
42
46
  EOH
43
47
 
44
48
  `bash -c '#{shell}'`
49
+ sleep(1)
45
50
  end
46
51
 
47
52
  def initialized?
@@ -0,0 +1,12 @@
1
+ class ProconBypassMan::YamlLoader
2
+ # @param [String] path
3
+ # @return [Hash]
4
+ def self.load(path: )
5
+ YAML.load_file(path).tap do |y|
6
+ # 行末に空白があるとto_yamlしたときに改行コードがエスケープされてしまうのでstrip
7
+ y.transform_values do |v|
8
+ v.strip! if v.is_a?(String)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ProconBypassMan
4
- VERSION = "0.1.22"
4
+ VERSION = "0.2.1"
5
5
  end
@@ -23,8 +23,7 @@ module ProconBypassMan
23
23
  ProconBypassMan.logger.info('websocket client: successfully connected in ProconBypassMan::Websocket::Client')
24
24
  }
25
25
  client.subscribed { |msg|
26
- ProconBypassMan.logger.info('websocket client: subscribed')
27
- puts({ event: :subscribed, msg: msg })
26
+ ProconBypassMan.logger.info("websocket client: subscribed(#{msg})")
28
27
  ProconBypassMan::SyncDeviceStatsJob.perform(ProconBypassMan::DeviceStatus.current)
29
28
  }
30
29
 
@@ -39,13 +38,11 @@ module ProconBypassMan
39
38
 
40
39
  client.disconnected {
41
40
  ProconBypassMan.logger.info('websocket client: disconnected!!')
42
- puts :disconnected
43
41
  client.reconnect!
44
42
  sleep 2
45
43
  }
46
44
  client.errored { |msg|
47
45
  ProconBypassMan.logger.error("websocket client: errored!!, #{msg}")
48
- puts :errored
49
46
  client.reconnect!
50
47
  sleep 2
51
48
  }