procon_bypass_man 0.1.22 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
  }