Skip to content

Commit

Permalink
fix(Overlay Mode): Overlay Mode Fixes
Browse files Browse the repository at this point in the history
- Temporary workaround for resource loading of SettingsManager and InputIconManager.
- Fixed loading and saving default target gamepad.
- Fixed loading the gamepad profile on startup in Overlay Mode, enabling
  opening overlay window.
- Added missing children to overlay_mode_card_ui.
- Fix loading GPU settings in PowerTools
  • Loading branch information
pastaq committed Nov 27, 2024
1 parent 3e066d2 commit 2e0443e
Show file tree
Hide file tree
Showing 12 changed files with 111 additions and 118 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ assets/crypto/keys/opengamepadui.pub: assets/crypto/keys/opengamepadui.key

.PHONY: deploy
deploy: dist-archive $(SSH_MOUNT_PATH)/.mounted ## Build, deploy, and tunnel to a remote device
cp dist/opengamepadui.tar.gz $(SSH_MOUNT_PATH)
scp dist/opengamepadui.tar.gz $(SSH_USER)@$(SSH_HOST):$(SSH_DATA_PATH)
cd $(SSH_MOUNT_PATH) #&& tar xvfz opengamepadui.tar.gz
ssh -t $(SSH_USER)@$(SSH_HOST) tar xvfz "$(SSH_DATA_PATH)/opengamepadui.tar.gz"

Expand Down
30 changes: 7 additions & 23 deletions core/global/launch_manager.gd
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,14 @@ func _init() -> void:

# Listen for signals from the primary Gamescope XWayland
if _xwayland_primary:
# When window focus changes, update the current app and gamepad profile
# Debug print when the focused window changes
var on_focus_changed := func(from: int, to: int):
if from == to:
return
logger.info("Window focus changed from " + str(from) + " to: " + str(to))
_xwayland_primary.focused_window_updated.connect(on_focus_changed)

# Debug print when the focused app changed
# When focused app changes, update the current app and gamepad profile
var on_focused_app_changed := func(from: int, to: int) -> void:
if from == to:
return
Expand Down Expand Up @@ -150,7 +150,7 @@ func _init() -> void:

in_game_state.state_entered.connect(on_game_state_entered)
in_game_state.state_exited.connect(on_game_state_exited)

set_gamepad_profile("")

# Loads persistent data like recent games launched, etc.
func _load_persist_data():
Expand Down Expand Up @@ -345,26 +345,10 @@ func set_gamepad_profile(path: String, target_gamepad: String = "") -> void:
# If no profile was specified, unset the gamepad profiles
if path == "":
# Try check to see if there is a global gamepad setting
var profile_path := settings_manager.get_value("input", "gamepad_profile", InputPlumber.DEFAULT_GLOBAL_PROFILE) as String
if not profile_path.ends_with(".json") or not FileAccess.file_exists(profile_path):
profile_path = InputPlumber.DEFAULT_GLOBAL_PROFILE
logger.info("Loading global gamepad profile: " + profile_path)

for gamepad in input_plumber.get_composite_devices():
InputPlumber.load_target_modified_profile(gamepad, profile_path, profile_modifier)

# Set the target gamepad if one was specified
if not target_gamepad.is_empty():
var target_devices := PackedStringArray([target_gamepad, "keyboard", "mouse"])
match target_gamepad:
"xb360", "xbox-series", "xbox-elite", "gamepad":
target_devices.append("touchpad")
_:
logger.debug(target_gamepad, "needs no additional target devices.")
logger.info("Setting target devices to: ", target_devices)
gamepad.set_target_devices(target_devices)

return
path = settings_manager.get_value("input", "gamepad_profile", InputPlumber.DEFAULT_GLOBAL_PROFILE) as String
# Verify we loaded a valid profile, or fallback.
if not path.ends_with(".json") or not FileAccess.file_exists(path):
path = InputPlumber.DEFAULT_GLOBAL_PROFILE

logger.info("Loading gamepad profile: " + path)
if not FileAccess.file_exists(path):
Expand Down
2 changes: 0 additions & 2 deletions core/systems/input/input_icon_manager.gd
Original file line number Diff line number Diff line change
Expand Up @@ -402,8 +402,6 @@ func _get_matching_event(path: String, input_type: InputType) -> Array[InputEven
## Set the last input type to the given value and emit a signal
func set_last_input_type(_last_input_type: InputType):
last_input_type = _last_input_type
if not self.disabled:
input_type_changed.emit(_last_input_type)


## Signal whenever a gamepad is connected/disconnected
Expand Down
4 changes: 4 additions & 0 deletions core/systems/input/input_icon_processor.gd
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,12 @@ func _input(event: InputEvent) -> void:
"InputEventJoypadMotion":
if abs(event.axis_value) > DEADZONE:
input_type = InputIconManager.InputType.GAMEPAD
var refresh := false
if input_type != icon_manager.last_input_type:
icon_manager.set_last_input_type(input_type)
refresh = true
if device_name != icon_manager.last_input_device:
icon_manager.last_input_device = device_name
refresh = true
if refresh:
icon_manager.refresh()
42 changes: 21 additions & 21 deletions core/systems/performance/performance_manager.gd
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ func load_or_create_profile(profile_path: String, library_item: LibraryLaunchIte
if profile:
logger.debug("Found profile at: " + profile_path)
return profile

# If the profile does not exist, create one with the currently applied
# performance settings.
logger.debug("No profile found. Creating one.")
Expand All @@ -163,18 +163,8 @@ func apply_profile(profile: PerformanceProfile) -> void:
if not _power_station.is_running():
logger.info("Unable to apply performance profile. PowerStation not detected.")
return

logger.info("Applying performance profile: " + profile.name)

# Apply CPU settings from the given profile
if _power_station.cpu:
logger.debug("Applying CPU performance settings from profile")
if _power_station.cpu.boost_enabled != profile.cpu_boost_enabled:
_power_station.cpu.boost_enabled = profile.cpu_boost_enabled
if _power_station.cpu.smt_enabled != profile.cpu_smt_enabled:
_power_station.cpu.smt_enabled = profile.cpu_smt_enabled
if profile.cpu_core_count_current > 0 and _power_station.cpu.cores_enabled != profile.cpu_core_count_current:
_power_station.cpu.cores_enabled = profile.cpu_core_count_current
logger.info("Applying performance profile: " + profile.name)

# Detect all GPU cards
var cards: Array[GpuCard] = []
Expand All @@ -187,6 +177,15 @@ func apply_profile(profile: PerformanceProfile) -> void:
if card.class != "integrated":
continue
logger.debug("Applying GPU performance settings from profile")
if profile.gpu_power_profile >= 0:
var power_profile := "max-performance"
if profile.gpu_power_profile == 0:
power_profile = "max-performance"
if profile.gpu_power_profile == 1:
power_profile = "power-saving"
if card.power_profile != power_profile:
logger.debug("Applying Power Profile: " + power_profile)
card.power_profile = power_profile
if card.manual_clock != profile.gpu_manual_enabled:
card.manual_clock = profile.gpu_manual_enabled
if profile.tdp_current > 0 and card.tdp != profile.tdp_current:
Expand All @@ -201,19 +200,20 @@ func apply_profile(profile: PerformanceProfile) -> void:
if profile.gpu_freq_max_current > 0 and card.clock_value_mhz_max != profile.gpu_freq_max_current:
logger.debug("Applying Clock Freq Max: " + str(profile.gpu_freq_max_current))
card.clock_value_mhz_max = profile.gpu_freq_max_current
if profile.gpu_power_profile >= 0:
var power_profile := "max-performance"
if profile.gpu_power_profile == 0:
power_profile = "max-performance"
if profile.gpu_power_profile == 1:
power_profile = "power-saving"
if card.power_profile != power_profile:
logger.debug("Applying Power Profile: " + power_profile)
card.power_profile = power_profile
if profile.gpu_temp_current > 0 and card.thermal_throttle_limit_c != profile.gpu_temp_current:
logger.debug("Applying Thermal Throttle Limit: " + str(profile.gpu_temp_current))
card.thermal_throttle_limit_c = profile.gpu_temp_current

# Apply CPU settings from the given profile
if _power_station.cpu:
logger.debug("Applying CPU performance settings from profile")
if _power_station.cpu.boost_enabled != profile.cpu_boost_enabled:
_power_station.cpu.boost_enabled = profile.cpu_boost_enabled
if _power_station.cpu.smt_enabled != profile.cpu_smt_enabled:
_power_station.cpu.smt_enabled = profile.cpu_smt_enabled
if profile.cpu_core_count_current > 0 and _power_station.cpu.cores_enabled != profile.cpu_core_count_current:
_power_station.cpu.cores_enabled = profile.cpu_core_count_current

logger.info("Applied Performance Profile: " + profile.name)
profile_applied.emit(profile)

Expand Down
83 changes: 38 additions & 45 deletions core/ui/card_ui/gamepad/gamepad_settings.gd
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,18 @@ func _ready() -> void:
self.gamepad_type_selected = item_selected
if self.profile:
var gamepad_type := self.get_selected_target_gamepad()
profile_gamepad = InputPlumberProfile.get_target_device_string(gamepad_type)
logger.debug("Setting gamepad to " + profile_gamepad)
self.profile_gamepad = InputPlumberProfile.get_target_device_string(gamepad_type)
logger.debug("Setting gamepad to " + self.profile_gamepad)
else:
logger.debug("No profile, unable to set gamepad type.")
self._update_mapping_elements()
gamepad_type_dropdown.item_selected.connect(on_gamepad_selected)

# Load the default profile
var profile_path = settings_manager.get_value("input", "gamepad_profile", "")
profile_gamepad = settings_manager.get_value("input", "gamepad_profile_target", "")
for gamepad in input_plumber.get_composite_devices():
_set_gamepad_profile(gamepad, profile_path)
self.profile_gamepad = settings_manager.get_value("input", "gamepad_profile_target", "")
for composite_device in input_plumber.get_composite_devices():
_set_gamepad_profile(composite_device, profile_path)

# Grab focus when the mapper exits
var on_state_changed := func(_from: State, to: State):
Expand Down Expand Up @@ -103,7 +103,7 @@ func _on_state_entered(_from: State) -> void:
main_container.visible = true

# Read from the state to determine which gamepad is being configured
gamepad = null
self.gamepad = null
if !gamepad_state.has_meta("dbus_path"):
logger.error("No gamepad was set to configure!")
# Make menu empty, unable to find gamepad to configure
Expand All @@ -116,56 +116,48 @@ func _on_state_entered(_from: State) -> void:
# Find the composite device to configure
for device: CompositeDevice in input_plumber.get_composite_devices():
if device.dbus_path == dbus_path:
gamepad = device
self.gamepad = device
break
if gamepad == null:
if self.gamepad == null:
logger.error("Unable to find CompositeDevice with path: " + dbus_path)
not_available.visible = true
main_container.visible = false
$ServiceNotAvailableContainer/Label.text = "No gamepad to configure"
return

logger.debug("Configuring gamepad '" + gamepad.name + "': " + dbus_path)
logger.debug("Configuring gamepad '" + self.gamepad.name + "': " + dbus_path)

# Set the gamepad name label
gamepad_label.text = gamepad.name
gamepad_label.text = self.gamepad.name

# Populate the menu with the source inputs for the given gamepad
populate_mappings_for(gamepad)
populate_mappings_for(self.gamepad)

# Set the library item, if one exists
library_item = null
profile = null
self.library_item = null
self.profile = null
if gamepad_state.has_meta("item"):
library_item = gamepad_state.get_meta("item") as LibraryItem
self.library_item = gamepad_state.get_meta("item") as LibraryItem

# If no library item was set, but there's a running app, try to see if
# there is a library item for it instead.
if not library_item:
library_item = launch_manager.get_current_app_library_item()
if not self.library_item:
self.library_item = launch_manager.get_current_app_library_item()

# If no library item was set with the state, then configure the OGUI profile
if not library_item:
profile_label.text = "Global"
@warning_ignore("confusable_local_declaration")
var profile_path := settings_manager.get_value("input", "gamepad_profile", InputPlumber.DEFAULT_GLOBAL_PROFILE) as String
var profile_target_gamepad := settings_manager.get_value("input", "gamepad_profile_target", "") as String
profile = _load_profile(profile_path)
profile_gamepad = profile_target_gamepad
_update_mapping_elements()
return

# Set the profile text to the game name
profile_label.text = library_item.name


# Check to see if the given game has a gamepad profile
var profile_path := settings_manager.get_library_value(library_item, "gamepad_profile", "") as String
var profile_target_gamepad := settings_manager.get_library_value(library_item, "gamepad_profile_target", "") as String
profile = _load_profile(profile_path)
profile_gamepad = profile_target_gamepad
_update_mapping_elements()
var profile_path: String
if not self.library_item:
self.profile_label.text = "Global"
profile_path = settings_manager.get_value("input", "gamepad_profile", InputPlumber.DEFAULT_GLOBAL_PROFILE) as String
else:
self.profile_label.text = self.library_item.name
profile_path = settings_manager.get_library_value(self.library_item, "gamepad_profile", "") as String

self.profile = _load_profile(profile_path)
self.profile_gamepad = settings_manager.get_library_value(self.library_item, "gamepad_profile_target", "") as String

_update_mapping_elements()

# Clear focus
mapping_focus_group.current_focus = null

Expand Down Expand Up @@ -402,8 +394,8 @@ func _update_mapping_elements() -> void:
profile_label.text = profile.name

# Update the dropdown based on the profile's target gamepad type
if not profile_gamepad.is_empty():
var target_device := InputPlumberProfile.get_target_device(profile_gamepad)
if not self.profile_gamepad.is_empty():
var target_device := InputPlumberProfile.get_target_device(self.profile_gamepad)
var gamepad_text := self.get_target_gamepad_text(target_device)
var i := 0
var idx := 0
Expand Down Expand Up @@ -612,16 +604,16 @@ func _set_gamepad_profile(gamepad: CompositeDevice, profile_path: String = "") -
profile_path = settings_manager.get_library_value(library_item, "gamepad_profile", "")

logger.debug("Setting " + gamepad.name + " to profile: " + profile_path)
InputPlumber.load_target_modified_profile(gamepad, profile_path, profile_gamepad)
InputPlumber.load_target_modified_profile(gamepad, profile_path, self.profile_gamepad)

# Set the target gamepad if one was specified
if not profile_gamepad.is_empty():
var target_devices := PackedStringArray([profile_gamepad, "keyboard", "mouse"])
match profile_gamepad:
if not self.profile_gamepad.is_empty():
var target_devices := PackedStringArray([self.profile_gamepad, "keyboard", "mouse"])
match self.profile_gamepad:
"xb360", "xbox-series", "xbox-elite", "gamepad":
target_devices.append("touchpad")
_:
logger.debug(profile_gamepad, "needs no additional target devices.")
logger.debug(self.profile_gamepad, "needs no additional target devices.")
logger.debug("Setting target devices to: ", target_devices)
gamepad.set_target_devices(target_devices)

Expand All @@ -645,8 +637,9 @@ func _save_profile() -> void:
return

# Update the game settings to use this global profile
self.logger.info("Saving gamepad profile at", path, "and gamepad target", self.profile_gamepad)
settings_manager.set_value("input", "gamepad_profile", path)
settings_manager.set_value("input", "gamepad_profile_target", profile_gamepad)
settings_manager.set_value("input", "gamepad_profile_target", self.profile_gamepad)

for gamepad in input_plumber.get_composite_devices():
_set_gamepad_profile(gamepad, path)
Expand All @@ -668,7 +661,7 @@ func _save_profile() -> void:
# Update the game settings to use this gamepad profile
var section := "game.{0}".format([library_item.name.to_lower()])
settings_manager.set_value(section, "gamepad_profile", path)
settings_manager.set_value(section, "gamepad_profile_target", profile_gamepad)
settings_manager.set_value(section, "gamepad_profile_target", self.profile_gamepad)
logger.debug("Saved gamepad profile to: " + path)
notify.text = "Gamepad profile saved"
notification_manager.show(notify)
Expand Down
4 changes: 2 additions & 2 deletions core/ui/card_ui/quick_bar/power_tools_card.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[ext_resource type="PackedScene" uid="uid://b5xnora73yd8x" path="res://core/ui/card_ui/quick_bar/qb_card.tscn" id="1_6lv34"]
[ext_resource type="PackedScene" uid="uid://dv3dt0j3jketh" path="res://core/ui/common/quick_bar/powertools_menu.tscn" id="2_votl1"]

[sub_resource type="Image" id="Image_n6cge"]
[sub_resource type="Image" id="Image_c7ewe"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
Expand All @@ -13,7 +13,7 @@ data = {
}

[sub_resource type="ImageTexture" id="ImageTexture_ca0vc"]
image = SubResource("Image_n6cge")
image = SubResource("Image_c7ewe")

[node name="PowerToolsCard" instance=ExtResource("1_6lv34")]
title = "Power Tools"
Expand Down
Loading

0 comments on commit 2e0443e

Please sign in to comment.