Compare commits
No commits in common. "main" and "with-banner" have entirely different histories.
main
...
with-banne
|
@ -1,9 +1,12 @@
|
|||
# CW Generator
|
||||
A simple application for generating self-made morse / CW audio files. All it features is a button as
|
||||
a morse key and allows you to export this as a WAV. Optionally, a MIDI keyboard can be used as a
|
||||
morse key (since godot 4.4 even on web).
|
||||
morse key.
|
||||
|
||||
## Known Limitations / TODO
|
||||
MIDI support only works locally for now.
|
||||
* webmidi might be an option: https://gist.github.com/srejv/b7198e25587e2d8e0a66860781b56852
|
||||
|
||||
Currently we can only export wav, as Godot itself doesn't include any encoders. Having the ability
|
||||
to export as mp3, ogg or opus would be nice, but external libraries are kind of a hassle. opus would
|
||||
be the nicest format, but doesn't seem to work with older Iphones.
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
#
|
||||
# © 2024-present https://github.com/cengiz-pz
|
||||
#
|
||||
|
||||
@tool
|
||||
class_name Share
|
||||
extends Node
|
||||
|
||||
const PLUGIN_SINGLETON_NAME: String = "SharePlugin"
|
||||
const MIME_TYPE_TEXT: String = "text/plain"
|
||||
const MIME_TYPE_IMAGE: String = "image/*"
|
||||
|
||||
@onready var _temp_image_path: String = OS.get_user_data_dir() + "/tmp_share_img_path.png"
|
||||
|
||||
var _plugin_singleton: Object
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if OS.get_name() == "Android":
|
||||
_update_plugin()
|
||||
|
||||
|
||||
func _notification(a_what: int) -> void:
|
||||
if a_what == NOTIFICATION_APPLICATION_RESUMED:
|
||||
_update_plugin()
|
||||
|
||||
|
||||
func _update_plugin() -> void:
|
||||
if _plugin_singleton == null:
|
||||
if Engine.has_singleton(PLUGIN_SINGLETON_NAME):
|
||||
_plugin_singleton = Engine.get_singleton(PLUGIN_SINGLETON_NAME)
|
||||
else:
|
||||
printerr("%s singleton not found!" % PLUGIN_SINGLETON_NAME)
|
||||
|
||||
|
||||
func share_text(a_title: String, a_subject: String, a_content: String) -> void:
|
||||
if _plugin_singleton != null:
|
||||
_plugin_singleton.share(
|
||||
SharedData.new()
|
||||
.set_title(a_title)
|
||||
.set_subject(a_subject)
|
||||
.set_content(a_content)
|
||||
.set_mime_type(MIME_TYPE_TEXT)
|
||||
.get_raw_data()
|
||||
)
|
||||
else:
|
||||
printerr("%s plugin not initialized" % PLUGIN_SINGLETON_NAME)
|
||||
|
||||
|
||||
func share_image(a_path: String, a_title: String, a_subject: String, a_content: String) -> void:
|
||||
share_file(a_path, MIME_TYPE_IMAGE, a_title, a_subject, a_content)
|
||||
|
||||
|
||||
func share_texture(a_texture: Texture2D, a_title: String, a_subject: String, a_content: String) -> void:
|
||||
var __image: Image = a_texture.get_image()
|
||||
__image.save_png(_temp_image_path)
|
||||
share_file(_temp_image_path, MIME_TYPE_IMAGE, a_title, a_subject, a_content)
|
||||
|
||||
|
||||
func share_viewport(a_viewport: Viewport, a_title: String, a_subject: String, a_content: String, a_flip_y: bool = false) -> void:
|
||||
var __image: Image = a_viewport.get_texture().get_image()
|
||||
if a_flip_y:
|
||||
__image.flip_y()
|
||||
__image.save_png(_temp_image_path)
|
||||
share_file(_temp_image_path, MIME_TYPE_IMAGE, a_title, a_subject, a_content)
|
||||
|
||||
|
||||
func share_file(a_path: String, a_mime_type: String, a_title: String, a_subject: String, a_content: String) -> void:
|
||||
if _plugin_singleton != null:
|
||||
_plugin_singleton.share(
|
||||
SharedData.new()
|
||||
.set_title(a_title)
|
||||
.set_subject(a_subject)
|
||||
.set_content(a_content)
|
||||
.set_mime_type(a_mime_type)
|
||||
.set_file_path(a_path)
|
||||
.get_raw_data()
|
||||
)
|
||||
else:
|
||||
printerr("%s plugin not initialized" % PLUGIN_SINGLETON_NAME)
|
|
@ -1 +0,0 @@
|
|||
uid://bjt60u6r1hqf7
|
|
@ -1,65 +0,0 @@
|
|||
#
|
||||
# © 2024-present https://github.com/cengiz-pz
|
||||
#
|
||||
|
||||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
const PLUGIN_NODE_TYPE_NAME = "Share"
|
||||
const PLUGIN_PARENT_NODE_TYPE = "Node"
|
||||
const PLUGIN_NAME: String = "SharePlugin"
|
||||
const PLUGIN_VERSION: String = "3.0"
|
||||
const PLUGIN_PACKAGE: String = "org.godotengine.plugin.android.share"
|
||||
const PLUGIN_DEPENDENCIES: Array = [ "androidx.appcompat:appcompat:1.7.0" ]
|
||||
|
||||
const PROVIDER_TAG = """
|
||||
<provider android:name="%s.ShareFileProvider"
|
||||
android:exported="false"
|
||||
android:authorities="%s.sharefileprovider"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_provider_paths"/>
|
||||
</provider>
|
||||
"""
|
||||
|
||||
var export_plugin: AndroidExportPlugin
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
add_custom_type(PLUGIN_NODE_TYPE_NAME, PLUGIN_PARENT_NODE_TYPE, preload("Share.gd"), preload("icon.png"))
|
||||
export_plugin = AndroidExportPlugin.new()
|
||||
add_export_plugin(export_plugin)
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
remove_custom_type(PLUGIN_NODE_TYPE_NAME)
|
||||
remove_export_plugin(export_plugin)
|
||||
export_plugin = null
|
||||
|
||||
|
||||
class AndroidExportPlugin extends EditorExportPlugin:
|
||||
var _plugin_name = PLUGIN_NAME
|
||||
|
||||
|
||||
func _supports_platform(platform: EditorExportPlatform) -> bool:
|
||||
if platform is EditorExportPlatformAndroid:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _get_android_libraries(platform: EditorExportPlatform, debug: bool) -> PackedStringArray:
|
||||
if debug:
|
||||
return PackedStringArray(["%s/bin/debug/%s-%s-debug.aar" % [_plugin_name, _plugin_name, PLUGIN_VERSION]])
|
||||
else:
|
||||
return PackedStringArray(["%s/bin/release/%s-%s-release.aar" % [_plugin_name, _plugin_name, PLUGIN_VERSION]])
|
||||
|
||||
|
||||
func _get_name() -> String:
|
||||
return _plugin_name
|
||||
|
||||
|
||||
func _get_android_dependencies(platform: EditorExportPlatform, debug: bool) -> PackedStringArray:
|
||||
return PackedStringArray(PLUGIN_DEPENDENCIES)
|
||||
|
||||
|
||||
func _get_android_manifest_application_element_contents(platform: EditorExportPlatform, debug: bool) -> String:
|
||||
return PROVIDER_TAG % [PLUGIN_PACKAGE, get_option("package/unique_name")]
|
|
@ -1 +0,0 @@
|
|||
uid://dpagp150p4gxb
|
Binary file not shown.
Binary file not shown.
BIN
addons/SharePlugin/icon.png (Stored with Git LFS)
BIN
addons/SharePlugin/icon.png (Stored with Git LFS)
Binary file not shown.
|
@ -1,34 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cm4eejjdmpyec"
|
||||
path="res://.godot/imported/icon.png-cbd45f0ce843eb69e82e7474c8559974.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/SharePlugin/icon.png"
|
||||
dest_files=["res://.godot/imported/icon.png-cbd45f0ce843eb69e82e7474c8559974.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
|
@ -1,47 +0,0 @@
|
|||
#
|
||||
# © 2024-present https://github.com/cengiz-pz
|
||||
#
|
||||
|
||||
class_name SharedData
|
||||
extends RefCounted
|
||||
|
||||
const DATA_KEY_TITLE = "title"
|
||||
const DATA_KEY_SUBJECT = "subject"
|
||||
const DATA_KEY_CONTENT = "content"
|
||||
const DATA_KEY_FILE_PATH = "file_path"
|
||||
const DATA_KEY_MIME_TYPE = "mime_type"
|
||||
|
||||
var _data: Dictionary
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
_data = {}
|
||||
|
||||
|
||||
func set_title(a_title: String) -> SharedData:
|
||||
_data[DATA_KEY_TITLE] = a_title
|
||||
return self
|
||||
|
||||
|
||||
func set_subject(a_subject: String) -> SharedData:
|
||||
_data[DATA_KEY_SUBJECT] = a_subject
|
||||
return self
|
||||
|
||||
|
||||
func set_content(a_content: String) -> SharedData:
|
||||
_data[DATA_KEY_CONTENT] = a_content
|
||||
return self
|
||||
|
||||
|
||||
func set_file_path(a_file_path: String) -> SharedData:
|
||||
_data[DATA_KEY_FILE_PATH] = a_file_path
|
||||
return self
|
||||
|
||||
|
||||
func set_mime_type(a_mime_type: String) -> SharedData:
|
||||
_data[DATA_KEY_MIME_TYPE] = a_mime_type
|
||||
return self
|
||||
|
||||
|
||||
func get_raw_data() -> Dictionary:
|
||||
return _data
|
|
@ -1 +0,0 @@
|
|||
uid://bceenkk1hoqml
|
|
@ -1,10 +0,0 @@
|
|||
;
|
||||
; © 2024-present https://github.com/cengiz-pz
|
||||
;
|
||||
[plugin]
|
||||
|
||||
name="Share"
|
||||
description="Allow sharing of text or images with other apps"
|
||||
author="Cengiz"
|
||||
version="3.0"
|
||||
script="ShareExportPlugin.gd"
|
|
@ -1 +0,0 @@
|
|||
uid://cswnldhb4jgfx
|
|
@ -1,9 +0,0 @@
|
|||
extends Node
|
||||
|
||||
var first_connect_done := false
|
||||
|
||||
func get_external_freq_param():
|
||||
match OS.get_name():
|
||||
"Web":
|
||||
return JavaScriptBridge.eval("new URL(window.location.href).searchParams.get('freq')")
|
||||
return null
|
|
@ -1 +0,0 @@
|
|||
uid://ua7dk3y0v21e
|
|
@ -10,10 +10,8 @@ export_filter="all_resources"
|
|||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="export/android/cwgenerator.apk"
|
||||
patches=PackedStringArray()
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
seed=0
|
||||
encrypt_pck=false
|
||||
encrypt_directory=false
|
||||
script_export_mode=2
|
||||
|
@ -47,10 +45,8 @@ package/show_as_launcher_app=false
|
|||
launcher_icons/main_192x192=""
|
||||
launcher_icons/adaptive_foreground_432x432=""
|
||||
launcher_icons/adaptive_background_432x432=""
|
||||
launcher_icons/adaptive_monochrome_432x432=""
|
||||
graphics/opengl_debug=false
|
||||
xr_features/xr_mode=0
|
||||
gesture/swipe_to_dismiss=false
|
||||
screen/immersive_mode=false
|
||||
screen/support_small=true
|
||||
screen/support_normal=true
|
||||
|
@ -66,7 +62,6 @@ permissions/access_checkin_properties=false
|
|||
permissions/access_coarse_location=false
|
||||
permissions/access_fine_location=false
|
||||
permissions/access_location_extra_commands=false
|
||||
permissions/access_media_location=false
|
||||
permissions/access_mock_location=false
|
||||
permissions/access_network_state=false
|
||||
permissions/access_surface_flinger=false
|
||||
|
@ -129,7 +124,7 @@ permissions/install_location_provider=false
|
|||
permissions/install_packages=false
|
||||
permissions/install_shortcut=false
|
||||
permissions/internal_system_window=false
|
||||
permissions/internet=true
|
||||
permissions/internet=false
|
||||
permissions/kill_background_processes=false
|
||||
permissions/location_hardware=false
|
||||
permissions/manage_accounts=false
|
||||
|
@ -154,10 +149,6 @@ permissions/read_frame_buffer=false
|
|||
permissions/read_history_bookmarks=false
|
||||
permissions/read_input_state=false
|
||||
permissions/read_logs=false
|
||||
permissions/read_media_audio=false
|
||||
permissions/read_media_images=false
|
||||
permissions/read_media_video=false
|
||||
permissions/read_media_visual_user_selected=false
|
||||
permissions/read_phone_state=false
|
||||
permissions/read_profile=false
|
||||
permissions/read_sms=false
|
||||
|
@ -227,10 +218,8 @@ export_filter="all_resources"
|
|||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="export/web/index.html"
|
||||
patches=PackedStringArray()
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
seed=0
|
||||
encrypt_pck=false
|
||||
encrypt_directory=false
|
||||
script_export_mode=2
|
||||
|
@ -258,367 +247,3 @@ progressive_web_app/icon_144x144=""
|
|||
progressive_web_app/icon_180x180=""
|
||||
progressive_web_app/icon_512x512=""
|
||||
progressive_web_app/background_color=Color(0, 0, 0, 1)
|
||||
|
||||
[preset.2]
|
||||
|
||||
name="Linux"
|
||||
platform="Linux"
|
||||
runnable=true
|
||||
advanced_options=false
|
||||
dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="export/lin/cwgenerator.x86_64"
|
||||
patches=PackedStringArray()
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
seed=0
|
||||
encrypt_pck=false
|
||||
encrypt_directory=false
|
||||
script_export_mode=2
|
||||
|
||||
[preset.2.options]
|
||||
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
debug/export_console_wrapper=1
|
||||
binary_format/embed_pck=false
|
||||
texture_format/s3tc_bptc=true
|
||||
texture_format/etc2_astc=false
|
||||
binary_format/architecture="x86_64"
|
||||
ssh_remote_deploy/enabled=false
|
||||
ssh_remote_deploy/host="user@host_ip"
|
||||
ssh_remote_deploy/port="22"
|
||||
ssh_remote_deploy/extra_args_ssh=""
|
||||
ssh_remote_deploy/extra_args_scp=""
|
||||
ssh_remote_deploy/run_script="#!/usr/bin/env bash
|
||||
export DISPLAY=:0
|
||||
unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\"
|
||||
\"{temp_dir}/{exe_name}\" {cmd_args}"
|
||||
ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash
|
||||
kill $(pgrep -x -f \"{temp_dir}/{exe_name} {cmd_args}\")
|
||||
rm -rf \"{temp_dir}\""
|
||||
|
||||
[preset.3]
|
||||
|
||||
name="macOS"
|
||||
platform="macOS"
|
||||
runnable=true
|
||||
advanced_options=false
|
||||
dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="export/mac/cwgenerator.zip"
|
||||
patches=PackedStringArray()
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
seed=0
|
||||
encrypt_pck=false
|
||||
encrypt_directory=false
|
||||
script_export_mode=2
|
||||
|
||||
[preset.3.options]
|
||||
|
||||
export/distribution_type=1
|
||||
binary_format/architecture="universal"
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
debug/export_console_wrapper=1
|
||||
application/icon=""
|
||||
application/icon_interpolation=4
|
||||
application/bundle_identifier="de.sebageek.cwgenerator"
|
||||
application/signature=""
|
||||
application/app_category="Games"
|
||||
application/short_version=""
|
||||
application/version=""
|
||||
application/copyright=""
|
||||
application/copyright_localized={}
|
||||
application/min_macos_version_x86_64="10.12"
|
||||
application/min_macos_version_arm64="11.00"
|
||||
application/export_angle=0
|
||||
display/high_res=true
|
||||
application/additional_plist_content=""
|
||||
xcode/platform_build="14C18"
|
||||
xcode/sdk_version="13.1"
|
||||
xcode/sdk_build="22C55"
|
||||
xcode/sdk_name="macosx13.1"
|
||||
xcode/xcode_version="1420"
|
||||
xcode/xcode_build="14C18"
|
||||
codesign/codesign=1
|
||||
codesign/installer_identity=""
|
||||
codesign/apple_team_id=""
|
||||
codesign/identity=""
|
||||
codesign/entitlements/custom_file=""
|
||||
codesign/entitlements/allow_jit_code_execution=false
|
||||
codesign/entitlements/allow_unsigned_executable_memory=false
|
||||
codesign/entitlements/allow_dyld_environment_variables=false
|
||||
codesign/entitlements/disable_library_validation=false
|
||||
codesign/entitlements/audio_input=false
|
||||
codesign/entitlements/camera=false
|
||||
codesign/entitlements/location=false
|
||||
codesign/entitlements/address_book=false
|
||||
codesign/entitlements/calendars=false
|
||||
codesign/entitlements/photos_library=false
|
||||
codesign/entitlements/apple_events=false
|
||||
codesign/entitlements/debugging=false
|
||||
codesign/entitlements/app_sandbox/enabled=false
|
||||
codesign/entitlements/app_sandbox/network_server=false
|
||||
codesign/entitlements/app_sandbox/network_client=false
|
||||
codesign/entitlements/app_sandbox/device_usb=false
|
||||
codesign/entitlements/app_sandbox/device_bluetooth=false
|
||||
codesign/entitlements/app_sandbox/files_downloads=0
|
||||
codesign/entitlements/app_sandbox/files_pictures=0
|
||||
codesign/entitlements/app_sandbox/files_music=0
|
||||
codesign/entitlements/app_sandbox/files_movies=0
|
||||
codesign/entitlements/app_sandbox/files_user_selected=0
|
||||
codesign/entitlements/app_sandbox/helper_executables=[]
|
||||
codesign/entitlements/additional=""
|
||||
codesign/custom_options=PackedStringArray()
|
||||
notarization/notarization=0
|
||||
privacy/microphone_usage_description=""
|
||||
privacy/microphone_usage_description_localized={}
|
||||
privacy/camera_usage_description=""
|
||||
privacy/camera_usage_description_localized={}
|
||||
privacy/location_usage_description=""
|
||||
privacy/location_usage_description_localized={}
|
||||
privacy/address_book_usage_description=""
|
||||
privacy/address_book_usage_description_localized={}
|
||||
privacy/calendar_usage_description=""
|
||||
privacy/calendar_usage_description_localized={}
|
||||
privacy/photos_library_usage_description=""
|
||||
privacy/photos_library_usage_description_localized={}
|
||||
privacy/desktop_folder_usage_description=""
|
||||
privacy/desktop_folder_usage_description_localized={}
|
||||
privacy/documents_folder_usage_description=""
|
||||
privacy/documents_folder_usage_description_localized={}
|
||||
privacy/downloads_folder_usage_description=""
|
||||
privacy/downloads_folder_usage_description_localized={}
|
||||
privacy/network_volumes_usage_description=""
|
||||
privacy/network_volumes_usage_description_localized={}
|
||||
privacy/removable_volumes_usage_description=""
|
||||
privacy/removable_volumes_usage_description_localized={}
|
||||
privacy/tracking_enabled=false
|
||||
privacy/tracking_domains=PackedStringArray()
|
||||
privacy/collected_data/name/collected=false
|
||||
privacy/collected_data/name/linked_to_user=false
|
||||
privacy/collected_data/name/used_for_tracking=false
|
||||
privacy/collected_data/name/collection_purposes=0
|
||||
privacy/collected_data/email_address/collected=false
|
||||
privacy/collected_data/email_address/linked_to_user=false
|
||||
privacy/collected_data/email_address/used_for_tracking=false
|
||||
privacy/collected_data/email_address/collection_purposes=0
|
||||
privacy/collected_data/phone_number/collected=false
|
||||
privacy/collected_data/phone_number/linked_to_user=false
|
||||
privacy/collected_data/phone_number/used_for_tracking=false
|
||||
privacy/collected_data/phone_number/collection_purposes=0
|
||||
privacy/collected_data/physical_address/collected=false
|
||||
privacy/collected_data/physical_address/linked_to_user=false
|
||||
privacy/collected_data/physical_address/used_for_tracking=false
|
||||
privacy/collected_data/physical_address/collection_purposes=0
|
||||
privacy/collected_data/other_contact_info/collected=false
|
||||
privacy/collected_data/other_contact_info/linked_to_user=false
|
||||
privacy/collected_data/other_contact_info/used_for_tracking=false
|
||||
privacy/collected_data/other_contact_info/collection_purposes=0
|
||||
privacy/collected_data/health/collected=false
|
||||
privacy/collected_data/health/linked_to_user=false
|
||||
privacy/collected_data/health/used_for_tracking=false
|
||||
privacy/collected_data/health/collection_purposes=0
|
||||
privacy/collected_data/fitness/collected=false
|
||||
privacy/collected_data/fitness/linked_to_user=false
|
||||
privacy/collected_data/fitness/used_for_tracking=false
|
||||
privacy/collected_data/fitness/collection_purposes=0
|
||||
privacy/collected_data/payment_info/collected=false
|
||||
privacy/collected_data/payment_info/linked_to_user=false
|
||||
privacy/collected_data/payment_info/used_for_tracking=false
|
||||
privacy/collected_data/payment_info/collection_purposes=0
|
||||
privacy/collected_data/credit_info/collected=false
|
||||
privacy/collected_data/credit_info/linked_to_user=false
|
||||
privacy/collected_data/credit_info/used_for_tracking=false
|
||||
privacy/collected_data/credit_info/collection_purposes=0
|
||||
privacy/collected_data/other_financial_info/collected=false
|
||||
privacy/collected_data/other_financial_info/linked_to_user=false
|
||||
privacy/collected_data/other_financial_info/used_for_tracking=false
|
||||
privacy/collected_data/other_financial_info/collection_purposes=0
|
||||
privacy/collected_data/precise_location/collected=false
|
||||
privacy/collected_data/precise_location/linked_to_user=false
|
||||
privacy/collected_data/precise_location/used_for_tracking=false
|
||||
privacy/collected_data/precise_location/collection_purposes=0
|
||||
privacy/collected_data/coarse_location/collected=false
|
||||
privacy/collected_data/coarse_location/linked_to_user=false
|
||||
privacy/collected_data/coarse_location/used_for_tracking=false
|
||||
privacy/collected_data/coarse_location/collection_purposes=0
|
||||
privacy/collected_data/sensitive_info/collected=false
|
||||
privacy/collected_data/sensitive_info/linked_to_user=false
|
||||
privacy/collected_data/sensitive_info/used_for_tracking=false
|
||||
privacy/collected_data/sensitive_info/collection_purposes=0
|
||||
privacy/collected_data/contacts/collected=false
|
||||
privacy/collected_data/contacts/linked_to_user=false
|
||||
privacy/collected_data/contacts/used_for_tracking=false
|
||||
privacy/collected_data/contacts/collection_purposes=0
|
||||
privacy/collected_data/emails_or_text_messages/collected=false
|
||||
privacy/collected_data/emails_or_text_messages/linked_to_user=false
|
||||
privacy/collected_data/emails_or_text_messages/used_for_tracking=false
|
||||
privacy/collected_data/emails_or_text_messages/collection_purposes=0
|
||||
privacy/collected_data/photos_or_videos/collected=false
|
||||
privacy/collected_data/photos_or_videos/linked_to_user=false
|
||||
privacy/collected_data/photos_or_videos/used_for_tracking=false
|
||||
privacy/collected_data/photos_or_videos/collection_purposes=0
|
||||
privacy/collected_data/audio_data/collected=false
|
||||
privacy/collected_data/audio_data/linked_to_user=false
|
||||
privacy/collected_data/audio_data/used_for_tracking=false
|
||||
privacy/collected_data/audio_data/collection_purposes=0
|
||||
privacy/collected_data/gameplay_content/collected=false
|
||||
privacy/collected_data/gameplay_content/linked_to_user=false
|
||||
privacy/collected_data/gameplay_content/used_for_tracking=false
|
||||
privacy/collected_data/gameplay_content/collection_purposes=0
|
||||
privacy/collected_data/customer_support/collected=false
|
||||
privacy/collected_data/customer_support/linked_to_user=false
|
||||
privacy/collected_data/customer_support/used_for_tracking=false
|
||||
privacy/collected_data/customer_support/collection_purposes=0
|
||||
privacy/collected_data/other_user_content/collected=false
|
||||
privacy/collected_data/other_user_content/linked_to_user=false
|
||||
privacy/collected_data/other_user_content/used_for_tracking=false
|
||||
privacy/collected_data/other_user_content/collection_purposes=0
|
||||
privacy/collected_data/browsing_history/collected=false
|
||||
privacy/collected_data/browsing_history/linked_to_user=false
|
||||
privacy/collected_data/browsing_history/used_for_tracking=false
|
||||
privacy/collected_data/browsing_history/collection_purposes=0
|
||||
privacy/collected_data/search_hhistory/collected=false
|
||||
privacy/collected_data/search_hhistory/linked_to_user=false
|
||||
privacy/collected_data/search_hhistory/used_for_tracking=false
|
||||
privacy/collected_data/search_hhistory/collection_purposes=0
|
||||
privacy/collected_data/user_id/collected=false
|
||||
privacy/collected_data/user_id/linked_to_user=false
|
||||
privacy/collected_data/user_id/used_for_tracking=false
|
||||
privacy/collected_data/user_id/collection_purposes=0
|
||||
privacy/collected_data/device_id/collected=false
|
||||
privacy/collected_data/device_id/linked_to_user=false
|
||||
privacy/collected_data/device_id/used_for_tracking=false
|
||||
privacy/collected_data/device_id/collection_purposes=0
|
||||
privacy/collected_data/purchase_history/collected=false
|
||||
privacy/collected_data/purchase_history/linked_to_user=false
|
||||
privacy/collected_data/purchase_history/used_for_tracking=false
|
||||
privacy/collected_data/purchase_history/collection_purposes=0
|
||||
privacy/collected_data/product_interaction/collected=false
|
||||
privacy/collected_data/product_interaction/linked_to_user=false
|
||||
privacy/collected_data/product_interaction/used_for_tracking=false
|
||||
privacy/collected_data/product_interaction/collection_purposes=0
|
||||
privacy/collected_data/advertising_data/collected=false
|
||||
privacy/collected_data/advertising_data/linked_to_user=false
|
||||
privacy/collected_data/advertising_data/used_for_tracking=false
|
||||
privacy/collected_data/advertising_data/collection_purposes=0
|
||||
privacy/collected_data/other_usage_data/collected=false
|
||||
privacy/collected_data/other_usage_data/linked_to_user=false
|
||||
privacy/collected_data/other_usage_data/used_for_tracking=false
|
||||
privacy/collected_data/other_usage_data/collection_purposes=0
|
||||
privacy/collected_data/crash_data/collected=false
|
||||
privacy/collected_data/crash_data/linked_to_user=false
|
||||
privacy/collected_data/crash_data/used_for_tracking=false
|
||||
privacy/collected_data/crash_data/collection_purposes=0
|
||||
privacy/collected_data/performance_data/collected=false
|
||||
privacy/collected_data/performance_data/linked_to_user=false
|
||||
privacy/collected_data/performance_data/used_for_tracking=false
|
||||
privacy/collected_data/performance_data/collection_purposes=0
|
||||
privacy/collected_data/other_diagnostic_data/collected=false
|
||||
privacy/collected_data/other_diagnostic_data/linked_to_user=false
|
||||
privacy/collected_data/other_diagnostic_data/used_for_tracking=false
|
||||
privacy/collected_data/other_diagnostic_data/collection_purposes=0
|
||||
privacy/collected_data/environment_scanning/collected=false
|
||||
privacy/collected_data/environment_scanning/linked_to_user=false
|
||||
privacy/collected_data/environment_scanning/used_for_tracking=false
|
||||
privacy/collected_data/environment_scanning/collection_purposes=0
|
||||
privacy/collected_data/hands/collected=false
|
||||
privacy/collected_data/hands/linked_to_user=false
|
||||
privacy/collected_data/hands/used_for_tracking=false
|
||||
privacy/collected_data/hands/collection_purposes=0
|
||||
privacy/collected_data/head/collected=false
|
||||
privacy/collected_data/head/linked_to_user=false
|
||||
privacy/collected_data/head/used_for_tracking=false
|
||||
privacy/collected_data/head/collection_purposes=0
|
||||
privacy/collected_data/other_data_types/collected=false
|
||||
privacy/collected_data/other_data_types/linked_to_user=false
|
||||
privacy/collected_data/other_data_types/used_for_tracking=false
|
||||
privacy/collected_data/other_data_types/collection_purposes=0
|
||||
ssh_remote_deploy/enabled=false
|
||||
ssh_remote_deploy/host="user@host_ip"
|
||||
ssh_remote_deploy/port="22"
|
||||
ssh_remote_deploy/extra_args_ssh=""
|
||||
ssh_remote_deploy/extra_args_scp=""
|
||||
ssh_remote_deploy/run_script="#!/usr/bin/env bash
|
||||
unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\"
|
||||
open \"{temp_dir}/{exe_name}.app\" --args {cmd_args}"
|
||||
ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash
|
||||
kill $(pgrep -x -f \"{temp_dir}/{exe_name}.app/Contents/MacOS/{exe_name} {cmd_args}\")
|
||||
rm -rf \"{temp_dir}\""
|
||||
application/min_macos_version="10.12"
|
||||
|
||||
[preset.4]
|
||||
|
||||
name="Windows Desktop"
|
||||
platform="Windows Desktop"
|
||||
runnable=true
|
||||
advanced_options=false
|
||||
dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="export/win/cwgenerator.exe"
|
||||
patches=PackedStringArray()
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
seed=0
|
||||
encrypt_pck=false
|
||||
encrypt_directory=false
|
||||
script_export_mode=2
|
||||
|
||||
[preset.4.options]
|
||||
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
debug/export_console_wrapper=1
|
||||
binary_format/embed_pck=false
|
||||
texture_format/s3tc_bptc=true
|
||||
texture_format/etc2_astc=false
|
||||
binary_format/architecture="x86_64"
|
||||
codesign/enable=false
|
||||
codesign/timestamp=true
|
||||
codesign/timestamp_server_url=""
|
||||
codesign/digest_algorithm=1
|
||||
codesign/description=""
|
||||
codesign/custom_options=PackedStringArray()
|
||||
application/modify_resources=true
|
||||
application/icon=""
|
||||
application/console_wrapper_icon=""
|
||||
application/icon_interpolation=4
|
||||
application/file_version=""
|
||||
application/product_version=""
|
||||
application/company_name=""
|
||||
application/product_name=""
|
||||
application/file_description=""
|
||||
application/copyright=""
|
||||
application/trademarks=""
|
||||
application/export_angle=0
|
||||
application/export_d3d12=0
|
||||
application/d3d12_agility_sdk_multiarch=true
|
||||
ssh_remote_deploy/enabled=false
|
||||
ssh_remote_deploy/host="user@host_ip"
|
||||
ssh_remote_deploy/port="22"
|
||||
ssh_remote_deploy/extra_args_ssh=""
|
||||
ssh_remote_deploy/extra_args_scp=""
|
||||
ssh_remote_deploy/run_script="Expand-Archive -LiteralPath '{temp_dir}\\{archive_name}' -DestinationPath '{temp_dir}'
|
||||
$action = New-ScheduledTaskAction -Execute '{temp_dir}\\{exe_name}' -Argument '{cmd_args}'
|
||||
$trigger = New-ScheduledTaskTrigger -Once -At 00:00
|
||||
$settings = New-ScheduledTaskSettingsSet
|
||||
$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings
|
||||
Register-ScheduledTask godot_remote_debug -InputObject $task -Force:$true
|
||||
Start-ScheduledTask -TaskName godot_remote_debug
|
||||
while (Get-ScheduledTask -TaskName godot_remote_debug | ? State -eq running) { Start-Sleep -Milliseconds 100 }
|
||||
Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue"
|
||||
ssh_remote_deploy/cleanup_script="Stop-ScheduledTask -TaskName godot_remote_debug -ErrorAction:SilentlyContinue
|
||||
Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue
|
||||
Remove-Item -Recurse -Force '{temp_dir}'"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bkoamufjn5wa1"
|
||||
uid="uid://dvaugiwdmfmge"
|
||||
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
|
|
|
@ -11,16 +11,14 @@ config_version=5
|
|||
[application]
|
||||
|
||||
config/name="cw generator"
|
||||
config/version="0.1.0"
|
||||
run/main_scene="res://scenes/main.tscn"
|
||||
config/features=PackedStringArray("4.4", "Mobile")
|
||||
config/features=PackedStringArray("4.3", "Mobile")
|
||||
run/max_fps=120
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[autoload]
|
||||
|
||||
MorseState="*res://autoloads/morse_state.gd"
|
||||
Utils="*res://autoloads/utils.gd"
|
||||
|
||||
[display]
|
||||
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
[gd_resource type="Theme" format=3 uid="uid://xxoc27tvaiut"]
|
||||
|
||||
[resource]
|
||||
default_font_size = 30
|
|
@ -1,6 +1,6 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://xqic6oa5d7oc"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://j1oei8suq5sj" path="res://scenes/morse_banner.gd" id="1_475pl"]
|
||||
[ext_resource type="Script" path="res://scenes/morse_banner.gd" id="1_475pl"]
|
||||
|
||||
[node name="MorseBanner" type="Control"]
|
||||
custom_minimum_size = Vector2(200, 100)
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
[gd_scene load_steps=3 format=3 uid="uid://ug3u6jf36dst"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b1k6j1jti114u" path="res://scenes/multi_morse_banner.gd" id="1_a1ve8"]
|
||||
|
||||
[sub_resource type="AudioStreamGenerator" id="AudioStreamGenerator_a1ve8"]
|
||||
mix_rate = 22050.0
|
||||
|
||||
[node name="MorseBanner" type="Control"]
|
||||
custom_minimum_size = Vector2(200, 100)
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_a1ve8")
|
||||
|
||||
[node name="Player" type="AudioStreamPlayer" parent="."]
|
||||
stream = SubResource("AudioStreamGenerator_a1ve8")
|
||||
volume_db = -100.0
|
||||
stream_paused = true
|
|
@ -1,153 +0,0 @@
|
|||
[gd_scene load_steps=3 format=3 uid="uid://dnxcrx04kl3xy"]
|
||||
|
||||
[ext_resource type="Theme" uid="uid://xxoc27tvaiut" path="res://scenes/GUITheme.tres" id="1_2wc0w"]
|
||||
[ext_resource type="Script" uid="uid://di8r70441xdms" path="res://scenes/multiplayer_connect.gd" id="1_uyd8l"]
|
||||
|
||||
[node name="MultiplayerConnect" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme = ExtResource("1_2wc0w")
|
||||
script = ExtResource("1_uyd8l")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="ConnectView" type="Control" parent="VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/ConnectView"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/ConnectView/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="FrequencyCreator" type="TextEdit" parent="VBoxContainer/ConnectView/VBoxContainer/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 20
|
||||
text = "430.200"
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer/ConnectView/VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "MHz"
|
||||
|
||||
[node name="CreateButton" type="Button" parent="VBoxContainer/ConnectView/VBoxContainer/HBoxContainer"]
|
||||
custom_minimum_size = Vector2(0, 100)
|
||||
layout_mode = 2
|
||||
size_flags_stretch_ratio = 2.41
|
||||
text = "Create Frequency"
|
||||
|
||||
[node name="BackButton" type="Button" parent="VBoxContainer/ConnectView/VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 10
|
||||
text = "Back"
|
||||
|
||||
[node name="RefreshButton" type="Button" parent="VBoxContainer/ConnectView/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Refresh"
|
||||
|
||||
[node name="FreqList" type="ItemList" parent="VBoxContainer/ConnectView/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="MorseView" type="Control" parent="VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/MorseView"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/MorseView/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer/MorseView/VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Freq:"
|
||||
|
||||
[node name="FreqLabel" type="Label" parent="VBoxContainer/MorseView/VBoxContainer/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="LeaveButton" type="Button" parent="VBoxContainer/MorseView/VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 10
|
||||
text = "Leave Frequency"
|
||||
|
||||
[node name="PlayerContainer" type="VBoxContainer" parent="VBoxContainer/MorseView/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="MorseButton" type="Button" parent="VBoxContainer/MorseView/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
text = "MORSE"
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Status:"
|
||||
|
||||
[node name="StatusLabel" type="Label" parent="VBoxContainer/HBoxContainer/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Disconnected"
|
||||
|
||||
[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer/HBoxContainer2"]
|
||||
layout_mode = 2
|
||||
mouse_filter = 0
|
||||
text = "Last Error:"
|
||||
|
||||
[node name="ErrorLabel" type="Label" parent="VBoxContainer/HBoxContainer/HBoxContainer2"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
mouse_filter = 0
|
||||
text = "<None>"
|
||||
autowrap_mode = 2
|
||||
|
||||
[connection signal="pressed" from="VBoxContainer/ConnectView/VBoxContainer/HBoxContainer/CreateButton" to="." method="_on_create_button_pressed"]
|
||||
[connection signal="pressed" from="VBoxContainer/ConnectView/VBoxContainer/HBoxContainer/BackButton" to="." method="_on_back_button_pressed"]
|
||||
[connection signal="pressed" from="VBoxContainer/ConnectView/VBoxContainer/RefreshButton" to="." method="_on_refresh_button_pressed"]
|
||||
[connection signal="item_clicked" from="VBoxContainer/ConnectView/VBoxContainer/FreqList" to="." method="_on_freq_list_join"]
|
||||
[connection signal="pressed" from="VBoxContainer/MorseView/VBoxContainer/HBoxContainer/LeaveButton" to="." method="_on_leave_button_pressed"]
|
||||
[connection signal="button_down" from="VBoxContainer/MorseView/VBoxContainer/MorseButton" to="." method="_on_morse_button_button_down"]
|
||||
[connection signal="button_up" from="VBoxContainer/MorseView/VBoxContainer/MorseButton" to="." method="_on_morse_button_button_up"]
|
||||
[connection signal="gui_input" from="VBoxContainer/HBoxContainer/HBoxContainer2/Label" to="." method="_on_error_label_gui_input"]
|
||||
[connection signal="gui_input" from="VBoxContainer/HBoxContainer/HBoxContainer2/ErrorLabel" to="." method="_on_error_label_gui_input"]
|
|
@ -6,7 +6,6 @@ var phase = 0.0
|
|||
var morse_state := false
|
||||
var playback: AudioStreamPlayback = null
|
||||
var vol_on := -30
|
||||
var multiplayer_enabled: bool = true # FIXME: maybe make this a tag?
|
||||
|
||||
func _process(_delta):
|
||||
fill_buffer()
|
||||
|
@ -20,30 +19,14 @@ func fill_buffer():
|
|||
phase = fmod(phase + increment, 1.0)
|
||||
|
||||
func _ready():
|
||||
match OS.get_name():
|
||||
"Android":
|
||||
%WavButton.text = "Share Wav"
|
||||
"Web":
|
||||
%WavButton.text = "Download Wav"
|
||||
|
||||
$Player.stream.mix_rate = sample_hz
|
||||
$Player.volume_db = -100
|
||||
$Player.play()
|
||||
playback = $Player.get_stream_playback()
|
||||
fill_buffer()
|
||||
|
||||
if OS.get_name() != "Web":
|
||||
OS.open_midi_inputs()
|
||||
print(OS.get_connected_midi_inputs())
|
||||
else:
|
||||
%MidiButton.visible = true
|
||||
|
||||
if multiplayer_enabled:
|
||||
%MultiplayerButton.visible = true
|
||||
|
||||
if Utils.get_external_freq_param():
|
||||
print("Direct connect to external freq: ", Utils.get_external_freq_param())
|
||||
_on_multiplayer_button_pressed()
|
||||
|
||||
OS.open_midi_inputs()
|
||||
print(OS.get_connected_midi_inputs())
|
||||
|
||||
func set_morse_state(state: bool):
|
||||
MorseState.set_state(state)
|
||||
|
@ -116,7 +99,7 @@ func _on_wav_button_pressed() -> void:
|
|||
# save wav
|
||||
var wav := AudioStreamWAV.new()
|
||||
wav.format = AudioStreamWAV.FORMAT_8_BITS
|
||||
wav.mix_rate = int(sample_hz)
|
||||
wav.mix_rate = sample_hz
|
||||
wav.stereo = false
|
||||
wav.data = data
|
||||
|
||||
|
@ -124,23 +107,8 @@ func _on_wav_button_pressed() -> void:
|
|||
var proposed_fname := "morse-" + Time.get_datetime_string_from_system(true) + ".wav"
|
||||
match OS.get_name():
|
||||
"Linux", "macOS", "Windows":
|
||||
print("Save on ", OS.get_name())
|
||||
|
||||
# create file dialog
|
||||
var fd := FileDialog.new()
|
||||
fd.title = "Save Wav file"
|
||||
fd.use_native_dialog = true
|
||||
fd.add_filter("*.wav", "WAV files")
|
||||
fd.current_file = proposed_fname
|
||||
|
||||
var save_method := func _save(path: String):
|
||||
print("Save as ", path)
|
||||
wav.save_to_wav(path)
|
||||
|
||||
fd.file_selected.connect(save_method)
|
||||
|
||||
add_child(fd)
|
||||
fd.popup_centered()
|
||||
wav.save_to_wav("/tmp/foo.wav")
|
||||
print("GLobalized path: " + ProjectSettings.globalize_path("user://morse.wav"))
|
||||
"Android":
|
||||
print("Sharing file on android")
|
||||
var tmp_file_path := OS.get_user_data_dir().path_join(proposed_fname)
|
||||
|
@ -161,12 +129,3 @@ func _on_wav_button_pressed() -> void:
|
|||
|
||||
func _on_reset_button_pressed() -> void:
|
||||
MorseState.reset()
|
||||
|
||||
|
||||
func _on_multiplayer_button_pressed() -> void:
|
||||
get_tree().change_scene_to_file("res://scenes/MultiplayerConnect.tscn")
|
||||
|
||||
|
||||
func _on_midi_button_pressed() -> void:
|
||||
OS.open_midi_inputs()
|
||||
print(OS.get_connected_midi_inputs())
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
uid://dmeokosn7gr27
|
|
@ -1,9 +1,8 @@
|
|||
[gd_scene load_steps=6 format=3 uid="uid://ctak1goemnnc5"]
|
||||
[gd_scene load_steps=5 format=3 uid="uid://ctak1goemnnc5"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dmeokosn7gr27" path="res://scenes/main.gd" id="1_8bx00"]
|
||||
[ext_resource type="Theme" uid="uid://xxoc27tvaiut" path="res://scenes/GUITheme.tres" id="1_jyhfs"]
|
||||
[ext_resource type="Script" path="res://scenes/main.gd" id="1_8bx00"]
|
||||
[ext_resource type="PackedScene" uid="uid://xqic6oa5d7oc" path="res://scenes/MorseBanner.tscn" id="2_v02md"]
|
||||
[ext_resource type="Script" uid="uid://bjt60u6r1hqf7" path="res://addons/SharePlugin/Share.gd" id="3_sugp2"]
|
||||
[ext_resource type="Script" path="res://addons/SharePlugin/Share.gd" id="3_ci1yg"]
|
||||
|
||||
[sub_resource type="AudioStreamGenerator" id="AudioStreamGenerator_kvn5v"]
|
||||
mix_rate = 11025.0
|
||||
|
@ -16,7 +15,6 @@ anchor_right = 1.0
|
|||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme = ExtResource("1_jyhfs")
|
||||
script = ExtResource("1_8bx00")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
|
@ -55,48 +53,24 @@ size_flags_stretch_ratio = 2.0
|
|||
text = "MORSE"
|
||||
|
||||
[node name="WavButton" type="Button" parent="VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
text = "Save Wav"
|
||||
text = "Write Wav"
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||
[node name="ResetButton" type="Button" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="MidiButton" type="Button" parent="VBoxContainer/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_stretch_ratio = 0.25
|
||||
text = "Open
|
||||
Midi"
|
||||
|
||||
[node name="ResetButton" type="Button" parent="VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
size_flags_stretch_ratio = 0.5
|
||||
text = "Reset"
|
||||
|
||||
[node name="MultiplayerButton" type="Button" parent="VBoxContainer/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 0.5
|
||||
text = "Connect to Frequency"
|
||||
|
||||
[node name="Player" type="AudioStreamPlayer" parent="."]
|
||||
stream = SubResource("AudioStreamGenerator_kvn5v")
|
||||
volume_db = -80.0
|
||||
|
||||
[node name="Share" type="Node" parent="."]
|
||||
script = ExtResource("3_sugp2")
|
||||
script = ExtResource("3_ci1yg")
|
||||
|
||||
[connection signal="button_down" from="VBoxContainer/MorseButton" to="." method="_on_morse_button_down"]
|
||||
[connection signal="button_up" from="VBoxContainer/MorseButton" to="." method="_on_morse_button_up"]
|
||||
[connection signal="pressed" from="VBoxContainer/WavButton" to="." method="_on_wav_button_pressed"]
|
||||
[connection signal="pressed" from="VBoxContainer/HBoxContainer/MidiButton" to="." method="_on_midi_button_pressed"]
|
||||
[connection signal="pressed" from="VBoxContainer/HBoxContainer/ResetButton" to="." method="_on_reset_button_pressed"]
|
||||
[connection signal="pressed" from="VBoxContainer/HBoxContainer/MultiplayerButton" to="." method="_on_multiplayer_button_pressed"]
|
||||
[connection signal="pressed" from="VBoxContainer/ResetButton" to="." method="_on_reset_button_pressed"]
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
uid://j1oei8suq5sj
|
|
@ -1,111 +0,0 @@
|
|||
@tool
|
||||
class_name MultiMorseBanner
|
||||
extends Control
|
||||
|
||||
static var sample_hz := 22050.0
|
||||
static var vol_on := -30
|
||||
var phase := 0.0
|
||||
|
||||
var num: int
|
||||
var tone_hz: int
|
||||
var color_on := Color(0, 128, 0)
|
||||
var color_off := Color(0, 0, 0)
|
||||
var last_delta := 0.0
|
||||
@export_range(0.1, 120.0) var display_sec := 5.0
|
||||
@export var stretch_display := false
|
||||
var playback;
|
||||
|
||||
func _ready() -> void:
|
||||
$Player.stream.mix_rate = sample_hz
|
||||
$Player.volume_db = -100
|
||||
$Player.play()
|
||||
playback = $Player.get_stream_playback()
|
||||
fill_buffer()
|
||||
|
||||
var morse_step_perc := 0.45
|
||||
const MMB_SCENE := preload("res://scenes/MultiMorseBanner.tscn")
|
||||
|
||||
class LocalMorseState:
|
||||
var states: Array[int] = []
|
||||
var curr_state := false
|
||||
var last_change: int = 0
|
||||
var start_time := 0
|
||||
|
||||
func reset() -> void:
|
||||
last_change = Time.get_ticks_msec()
|
||||
start_time = last_change
|
||||
states = []
|
||||
|
||||
func set_state(state: bool) -> void:
|
||||
if state == curr_state:
|
||||
return
|
||||
curr_state = state
|
||||
|
||||
var now := Time.get_ticks_msec()
|
||||
states.push_back(now - last_change)
|
||||
last_change = now
|
||||
|
||||
var morse_state := LocalMorseState.new()
|
||||
|
||||
static func new_banner(num: int, color: Color, tone: int):
|
||||
var mmb = MMB_SCENE.instantiate()
|
||||
mmb.num = num
|
||||
mmb.color_on = color
|
||||
mmb.tone_hz = tone
|
||||
|
||||
return mmb
|
||||
|
||||
func _draw_morse_rect(x: float, width: float, state: bool):
|
||||
var rect := Rect2(max(x, 0.0), morse_step_perc * size.y, width, (0.5 - morse_step_perc) * size.y)
|
||||
draw_rect(rect, color_on if state else color_off, true, -1.0, true)
|
||||
|
||||
func _draw():
|
||||
# black background
|
||||
draw_rect(Rect2(0.0, 0.0, size.x, size.y), Color.BLACK)
|
||||
|
||||
# in editor we only want a black rectangle
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
var morse_on := morse_state.curr_state
|
||||
var first_time := Time.get_ticks_msec() - morse_state.last_change
|
||||
var curr_x := float(size.x)
|
||||
|
||||
var px_per_s := 0.0
|
||||
if not stretch_display:
|
||||
px_per_s = size.x / display_sec
|
||||
else:
|
||||
px_per_s = size.x / (Time.get_ticks_msec() - morse_state.start_time) * 1000.0
|
||||
|
||||
for n in [-1] + range(morse_state.states.size() - 1, -1, -1):
|
||||
var duration := first_time if n == -1 else morse_state.states[n]
|
||||
var rect_width: float = min(duration / 1000.0 * px_per_s, curr_x)
|
||||
curr_x -= rect_width
|
||||
if morse_on:
|
||||
# at the moment we only draw the morse rects
|
||||
_draw_morse_rect(curr_x, rect_width, morse_on)
|
||||
morse_on = not morse_on
|
||||
if curr_x <= 0.0:
|
||||
break
|
||||
|
||||
func _process(_delta):
|
||||
last_delta += _delta
|
||||
queue_redraw()
|
||||
fill_buffer()
|
||||
|
||||
func set_morse_state(state: bool) -> void:
|
||||
morse_state.set_state(state)
|
||||
# morse_state = state
|
||||
if state:
|
||||
$Player.volume_db = vol_on
|
||||
else:
|
||||
$Player.volume_db = -100
|
||||
|
||||
func fill_buffer():
|
||||
var increment = tone_hz / sample_hz
|
||||
var frames_available = playback.get_frames_available()
|
||||
|
||||
for i in range(frames_available):
|
||||
playback.push_frame(Vector2.ONE * sin(phase * TAU))
|
||||
phase = fmod(phase + increment, 1.0)
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://b1k6j1jti114u
|
|
@ -1,252 +0,0 @@
|
|||
extends Control
|
||||
|
||||
var server := "seba-geek.de"
|
||||
var port := "3784"
|
||||
var ws_url := "wss://seba-geek.de/godot/cw-generator-ws/"
|
||||
var IS_DEBUG = false
|
||||
var ws := WebSocketPeer.new()
|
||||
var ws_last_status = -1
|
||||
var autoconnect_to_freq: String
|
||||
|
||||
var available_freqs = []
|
||||
var mmb_self: MultiMorseBanner
|
||||
var mmb_others: Dictionary[String, MultiMorseBanner]
|
||||
|
||||
class PlayerData:
|
||||
var num: int
|
||||
var color: Color
|
||||
var tone: int
|
||||
|
||||
func _init(num, color, tone):
|
||||
self.num = num
|
||||
self.color = color
|
||||
self.tone = tone
|
||||
|
||||
var player_data := [
|
||||
PlayerData.new(0, Color( 0, 255, 0), 880),
|
||||
PlayerData.new(1, Color(255, 0, 0), 1318),
|
||||
PlayerData.new(2, Color( 0, 0, 255), 587),
|
||||
PlayerData.new(3, Color(255, 0, 255), 440),
|
||||
PlayerData.new(3, Color( 0, 255, 255), 783),
|
||||
PlayerData.new(3, Color(255, 255, 0), 1567),
|
||||
]
|
||||
|
||||
func _ready() -> void:
|
||||
# FIXME: connection handling / reconnect
|
||||
# FIXME: status / error messages
|
||||
# FIXME: randomize default join frquency
|
||||
# FIXME: automatic refresh
|
||||
_connect_ws()
|
||||
|
||||
if not Utils.first_connect_done:
|
||||
var cmdline_freq = Utils.get_external_freq_param()
|
||||
if cmdline_freq:
|
||||
autoconnect_to_freq = cmdline_freq
|
||||
|
||||
func _connect_ws():
|
||||
if not IS_DEBUG:
|
||||
print("Connecting to ", ws_url)
|
||||
ws.connect_to_url(ws_url)
|
||||
else:
|
||||
print("ws://%s:%s" % [server, port])
|
||||
ws.connect_to_url("ws://%s:%s" % [server, port])
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
ws.poll()
|
||||
var state := ws.get_ready_state()
|
||||
while state == WebSocketPeer.STATE_OPEN and ws.get_available_packet_count():
|
||||
_handle_packet()
|
||||
if ws_last_status != state:
|
||||
match state:
|
||||
WebSocketPeer.STATE_CONNECTING:
|
||||
%StatusLabel.text = "Connecting...!"
|
||||
WebSocketPeer.STATE_OPEN:
|
||||
%StatusLabel.text = "Connected!"
|
||||
WebSocketPeer.STATE_CLOSING:
|
||||
%StatusLabel.text = "Disconnecting..."
|
||||
WebSocketPeer.STATE_CLOSED:
|
||||
%StatusLabel.text = "Disconnected :("
|
||||
|
||||
# Trigger reconnect
|
||||
_trigger_reconnect(1)
|
||||
ws_last_status = state
|
||||
|
||||
func _trigger_reconnect(delay: int):
|
||||
await get_tree().create_timer(delay).timeout
|
||||
_connect_ws()
|
||||
|
||||
func _handle_packet() -> void:
|
||||
var data := ws.get_packet().get_string_from_utf8()
|
||||
var parsed: Dictionary = JSON.parse_string(data)
|
||||
if typeof(parsed) != TYPE_DICTIONARY or not parsed.has("type"):
|
||||
print("Error: Could not parse data: ", data)
|
||||
return
|
||||
|
||||
print("Recvd data: ", parsed)
|
||||
|
||||
match parsed["type"]:
|
||||
"hello":
|
||||
# fetch frequency list on first join
|
||||
_on_refresh_button_pressed()
|
||||
|
||||
if autoconnect_to_freq:
|
||||
send_data({"cmd": "create", "freq": autoconnect_to_freq, "join-if-present": true})
|
||||
"join":
|
||||
_join_freq(parsed["freq"], parsed["self_id"], parsed["other_players"])
|
||||
|
||||
"freq-list":
|
||||
%FreqList.clear()
|
||||
for freq in parsed["freqs"]:
|
||||
var text = "%s MHz (%d present)" % [freq["freq"], freq["players"]]
|
||||
print("Adding ", text)
|
||||
var idx: int = %FreqList.add_item(text)
|
||||
%FreqList.set_item_metadata(idx, freq)
|
||||
|
||||
"morse-state":
|
||||
var from_player: String = parsed["from_player"]
|
||||
if from_player not in mmb_others:
|
||||
print("Error: Got morse state from unknown player ", from_player)
|
||||
return
|
||||
mmb_others[from_player].set_morse_state(parsed["state"])
|
||||
|
||||
"player-joined":
|
||||
var new_player: String = parsed["player"]
|
||||
if new_player in mmb_others:
|
||||
print("Error: got player join message, but player ", new_player, " is already present")
|
||||
return
|
||||
|
||||
# find free player id
|
||||
var player_n := 1
|
||||
while true:
|
||||
var found := false
|
||||
for other in mmb_others.values():
|
||||
if other.num == player_n:
|
||||
print("New player, num ", player_n, " already taken")
|
||||
found = true
|
||||
break
|
||||
if not found:
|
||||
break
|
||||
player_n += 1
|
||||
|
||||
make_player(player_n, new_player)
|
||||
|
||||
"player-left":
|
||||
var player: String = parsed["player"]
|
||||
if player not in mmb_others:
|
||||
print("Error: player not part of freq ", player)
|
||||
return
|
||||
remove_player(parsed["player"])
|
||||
"leave":
|
||||
_leave_freq()
|
||||
"error":
|
||||
%ErrorLabel.text = parsed["message"]
|
||||
_:
|
||||
print("Unhandled message: ", parsed["type"])
|
||||
|
||||
|
||||
func _join_freq(freq: String, player_id: String, other_players: Array):
|
||||
for child in %PlayerContainer.get_children():
|
||||
%PlayerContainer.remove_child(child)
|
||||
|
||||
mmb_self = make_player(0, player_id)
|
||||
for n in range(other_players.size()):
|
||||
make_player(n + 1, other_players[n])
|
||||
|
||||
%FreqLabel.text = "%s MHz" % freq
|
||||
%ConnectView.hide()
|
||||
%MorseView.show()
|
||||
|
||||
func _leave_freq():
|
||||
%MorseView.hide()
|
||||
if mmb_self:
|
||||
mmb_self.set_morse_state(false)
|
||||
mmb_self = null
|
||||
|
||||
for mmb: MultiMorseBanner in mmb_others.values():
|
||||
mmb.set_morse_state(false)
|
||||
|
||||
mmb_others.clear()
|
||||
for child in %PlayerContainer.get_children():
|
||||
%PlayerContainer.remove_child(child)
|
||||
%ConnectView.show()
|
||||
|
||||
func _on_refresh_button_pressed() -> void:
|
||||
var refresh_cmd := {"cmd": "list", "type": "cw-generator"}
|
||||
var data := JSON.stringify(refresh_cmd) + "\n"
|
||||
ws.send_text(data)
|
||||
|
||||
|
||||
func _on_create_button_pressed() -> void:
|
||||
var freq: String = "%.3f" % float(%FrequencyCreator.text)
|
||||
var refresh_cmd := {"cmd": "create", "type": "cw-generator", "freq": freq}
|
||||
var data := JSON.stringify(refresh_cmd) + "\n"
|
||||
ws.send_text(data)
|
||||
|
||||
|
||||
func _on_freq_list_join(index: int, at_position: Vector2, mouse_button_index: int) -> void:
|
||||
var meta = %FreqList.get_item_metadata(index)
|
||||
var freq: String = meta["freq"]
|
||||
print("Yop ", index, " metadata ", freq)
|
||||
var join_cmd := {"cmd": "join", "freq": freq, "type": "cw-generator"}
|
||||
ws.send_text(JSON.stringify(join_cmd) + "\n")
|
||||
|
||||
func make_player(no: int, player_id: String):
|
||||
var pd: PlayerData
|
||||
if no < player_data.size():
|
||||
pd = player_data[no]
|
||||
else:
|
||||
pd = PlayerData.new(no, Color(randi_range(0, 255), randi_range(0, 255), randi_range(0, 255)), randi_range(440, 440 * 3))
|
||||
|
||||
var mmb = MultiMorseBanner.new_banner(pd.num, pd.color, pd.tone)
|
||||
%PlayerContainer.add_child(mmb)
|
||||
mmb_others[player_id] = mmb
|
||||
return mmb
|
||||
|
||||
func remove_player(player_id: String):
|
||||
mmb_others[player_id].set_morse_state(false)
|
||||
%PlayerContainer.remove_child(mmb_others[player_id])
|
||||
mmb_others.erase(player_id)
|
||||
|
||||
func set_morse_state(state: bool):
|
||||
mmb_self.set_morse_state(state)
|
||||
send_data({"cmd": "morse-state", "state": state})
|
||||
|
||||
func send_data(data: Dictionary):
|
||||
var text := JSON.stringify(data) + "\n"
|
||||
ws.send_text(text)
|
||||
|
||||
func _on_morse_button_button_down() -> void:
|
||||
if not mmb_self:
|
||||
return
|
||||
set_morse_state(true)
|
||||
|
||||
func _on_morse_button_button_up() -> void:
|
||||
if not mmb_self:
|
||||
return
|
||||
set_morse_state(false)
|
||||
|
||||
func _input(input_event):
|
||||
if input_event is InputEventMIDI:
|
||||
_process_midi_event(input_event)
|
||||
|
||||
func _process_midi_event(midi_event):
|
||||
if not mmb_self:
|
||||
return
|
||||
|
||||
if midi_event.channel in [0, 9]:
|
||||
if midi_event.message == MIDI_MESSAGE_NOTE_ON:
|
||||
set_morse_state(true)
|
||||
elif midi_event.message == MIDI_MESSAGE_NOTE_OFF:
|
||||
set_morse_state(false)
|
||||
|
||||
func _on_leave_button_pressed() -> void:
|
||||
send_data({"cmd": "leave"})
|
||||
|
||||
|
||||
func _on_error_label_gui_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton and event.pressed and event.button_index == 1:
|
||||
%ErrorLabel.text = "<cleared>"
|
||||
|
||||
|
||||
func _on_back_button_pressed() -> void:
|
||||
get_tree().change_scene_to_file("res://scenes/main.tscn")
|
|
@ -1 +0,0 @@
|
|||
uid://di8r70441xdms
|
|
@ -1,205 +0,0 @@
|
|||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
|
||||
from websockets.asyncio.connection import broadcast
|
||||
from websockets.asyncio.server import serve
|
||||
|
||||
__VERSION__ = "0.0.1"
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s %(levelname)s %(message)s"
|
||||
)
|
||||
|
||||
LOG = logging.getLogger()
|
||||
|
||||
|
||||
class Client:
|
||||
freqs = {}
|
||||
freq_re = re.compile(r"^\d+\.\d{3}$")
|
||||
|
||||
def __init__(self, websocket):
|
||||
self.websocket = websocket
|
||||
self.curr_freq = None
|
||||
|
||||
async def handle(self):
|
||||
LOG.info(">>> New client %s connected", self.client)
|
||||
exc = None
|
||||
try:
|
||||
await self._handle_client()
|
||||
except Exception as e:
|
||||
exc = e
|
||||
finally:
|
||||
# FIXME: basically handle disconnect / leave from room
|
||||
LOG.info("<<< Client %s id %s disconnected: %s", self.client, self.id, exc)
|
||||
if self.curr_freq:
|
||||
await self._leave_room()
|
||||
|
||||
@property
|
||||
def client(self):
|
||||
ip, port, *_ = self.websocket.remote_address
|
||||
if ':' in ip:
|
||||
ip = f"[{ip}]"
|
||||
return f"{ip}:{port}"
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return str(self.websocket.id)
|
||||
|
||||
async def _handle_client(self):
|
||||
await self._send(type="hello", name="LobbySrv 3000", version=__VERSION__)
|
||||
async for data in self.websocket:
|
||||
try:
|
||||
data = json.loads(data)
|
||||
except json.JSONDecodeError:
|
||||
self._send_error("Could not decode message, invalid json")
|
||||
LOG.error("client %s sent broken data %s", self.client, repr(data))
|
||||
continue
|
||||
|
||||
if not isinstance(data, dict) or "cmd" not in data:
|
||||
await self._send_error("Invalid format in json")
|
||||
LOG.error("client %s sent broken data (no cmd key in data) %s", self.client, repr(data))
|
||||
continue
|
||||
|
||||
LOG.info("client %s wrote: %s", self.client, data)
|
||||
|
||||
match data["cmd"]:
|
||||
case "quit":
|
||||
break
|
||||
case "create":
|
||||
await self._create_room(data)
|
||||
case "join":
|
||||
await self._join_room(data)
|
||||
case "leave":
|
||||
await self._leave_room()
|
||||
case "list":
|
||||
freqs = [{"freq": freq, "players": len(players)}
|
||||
for freq, players in self.freqs.items()]
|
||||
await self._send(type="freq-list", freqs=freqs)
|
||||
case "disconnect":
|
||||
pass
|
||||
case "morse-state":
|
||||
await self._handle_morse_state(data)
|
||||
case _:
|
||||
await self._send_error("Unknown command")
|
||||
|
||||
async def _create_room(self, data):
|
||||
if self.curr_freq:
|
||||
await self._send_error(f"Already on frequency {self.curr_freq}")
|
||||
return
|
||||
|
||||
if "freq" not in data:
|
||||
await self._send_error("No frequency in create message")
|
||||
return
|
||||
freq = data["freq"]
|
||||
|
||||
if not self.freq_re.match(freq):
|
||||
await self._send_error("Invalid frequency")
|
||||
return
|
||||
|
||||
if freq in self.freqs:
|
||||
if data.get("join-if-present"):
|
||||
await self._join_room({"freq": freq})
|
||||
else:
|
||||
await self._send_error("Frequency already in use")
|
||||
return
|
||||
|
||||
self.curr_freq = freq
|
||||
self.freqs[freq] = [self]
|
||||
await self._send(type="join", freq=self.curr_freq, self_id=self.id, other_players=[])
|
||||
|
||||
async def _join_room(self, data):
|
||||
if self.curr_freq:
|
||||
await self._send_error(f"Already on frequency {self.curr_freq}")
|
||||
return
|
||||
|
||||
if "freq" not in data:
|
||||
await self._send_error("No frequency in join message")
|
||||
return
|
||||
freq = data["freq"]
|
||||
|
||||
if freq not in self.freqs:
|
||||
await self._send_error(f"Frequency {freq} not available")
|
||||
return
|
||||
|
||||
self.curr_freq = freq
|
||||
self.freqs[freq].append(self)
|
||||
# FIXME: do we need locking here?
|
||||
LOG.debug("FREQ %s %s %s", self.curr_freq, freq, self.freqs)
|
||||
await self._send(type="join", freq=self.curr_freq, self_id=self.id,
|
||||
other_players=[c.id for c in self._others(freq)])
|
||||
await self._send_to_group(self._others(freq), type="player-joined", player=self.id)
|
||||
|
||||
async def _handle_morse_state(self, data):
|
||||
if not self.curr_freq:
|
||||
await self._send_error("No frequency selected")
|
||||
return
|
||||
|
||||
if "state" not in data or not isinstance(data["state"], bool):
|
||||
await self._send_error("No state key with type bool in data")
|
||||
return
|
||||
|
||||
await self._send_to_group(self._others(self.curr_freq),
|
||||
type="morse-state", state=data["state"], from_player=self.id)
|
||||
|
||||
async def _leave_room(self):
|
||||
if self.curr_freq:
|
||||
await self._send_to_group(self._others(self.curr_freq),
|
||||
type="player-left", player=self.id)
|
||||
try:
|
||||
self.freqs[self.curr_freq].remove(self)
|
||||
except ValueError:
|
||||
LOG.warning("Player %s was not in freq %s", self.id, self.curr_freq)
|
||||
if not self.freqs[self.curr_freq]:
|
||||
del self.freqs[self.curr_freq]
|
||||
self.curr_freq = None
|
||||
else:
|
||||
LOG.warning("Client %s is not on a frequency, sending a 'leave' nontheless", self.client)
|
||||
|
||||
try:
|
||||
await self._send(type="leave")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _others(self, freq):
|
||||
return [c for c in self.freqs[freq] if c.id != self.id]
|
||||
|
||||
async def _send(self, ignore_exceptions=False, **kwargs):
|
||||
data = json.dumps(kwargs).encode()
|
||||
LOG.debug("--> sending out to %s: %s", self.client, data)
|
||||
try:
|
||||
await self.websocket.send(json.dumps(kwargs).encode() + b"\n")
|
||||
except Exception as e:
|
||||
LOG.error("Error sending data to %s: %s", self.client, e)
|
||||
if not ignore_exceptions:
|
||||
raise
|
||||
|
||||
async def _send_to_group(self, group, **kwargs):
|
||||
LOG.info("broadcast() to %s clients: %s", len(group), kwargs)
|
||||
broadcast([c.websocket for c in group], json.dumps(kwargs).encode() + b"\n")
|
||||
|
||||
async def _send_error(self, msg: str):
|
||||
await self._send(type="error", message=msg)
|
||||
|
||||
|
||||
async def new_client(websocket):
|
||||
try:
|
||||
client = Client(websocket)
|
||||
await client.handle()
|
||||
finally:
|
||||
pass
|
||||
# async for message in websocket:
|
||||
# await websocket.send(message)
|
||||
|
||||
|
||||
async def main():
|
||||
HOST, PORT = "0.0.0.0", 3784
|
||||
async with serve(new_client, HOST, PORT) as server:
|
||||
await server.serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
LOG.info("Starting server")
|
||||
asyncio.run(main())
|
|
@ -1,81 +0,0 @@
|
|||
import json
|
||||
import socketserver
|
||||
|
||||
__VERSION__ = "0.0.1"
|
||||
|
||||
# https://websockets.readthedocs.io/en/stable/reference/asyncio/server.html
|
||||
|
||||
|
||||
class LobbyMgr:
|
||||
__instance = None
|
||||
|
||||
def __new__(cls):
|
||||
if cls.__instance is None:
|
||||
cls.__instance = LobbyMgr()
|
||||
|
||||
return cls.__instance
|
||||
|
||||
|
||||
class LobbyHandler(socketserver.StreamRequestHandler):
|
||||
def handle(self):
|
||||
print(f" >>> New client {self.client} connected")
|
||||
exc = None
|
||||
try:
|
||||
self._handle_client()
|
||||
except Exception as e:
|
||||
exc = e
|
||||
finally:
|
||||
print(f" <<< Client {self.client} disconnected: {exc}")
|
||||
|
||||
@property
|
||||
def client(self):
|
||||
return f"{':'.join(map(str, self.client_address))}"
|
||||
|
||||
def _handle_client(self):
|
||||
self._send(type="hello", name="LobbySrv 3000", version=__VERSION__)
|
||||
while True:
|
||||
data = self.rfile.readline(10000).rstrip()
|
||||
print(f" <-- client {self.client} sent {repr(data)}")
|
||||
|
||||
if not data.strip():
|
||||
continue
|
||||
try:
|
||||
data = json.loads(data)
|
||||
except json.JSONDecodeError:
|
||||
self._send_error("Could not decode message, invalid json")
|
||||
continue
|
||||
|
||||
if not isinstance(data, dict) or "cmd" not in data:
|
||||
self._send_error("Invalid format in json")
|
||||
continue
|
||||
|
||||
print(f"{self.client_address[0]} wrote:", data)
|
||||
|
||||
match data["cmd"]:
|
||||
case "quit":
|
||||
break
|
||||
case _:
|
||||
self._send_error("Unknown command")
|
||||
|
||||
def _send(self, **kwargs):
|
||||
data = json.dumps(kwargs).encode()
|
||||
print(f" --> sending out to {self.client}: {data}")
|
||||
self.wfile.write(json.dumps(kwargs).encode() + b"\n")
|
||||
|
||||
def _send_error(self, msg: str):
|
||||
self._send(type="error", message=msg)
|
||||
|
||||
|
||||
class LobbyServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||
allow_reuse_address = True
|
||||
|
||||
|
||||
def main():
|
||||
HOST, PORT = "localhost", 3784
|
||||
|
||||
with LobbyServer((HOST, PORT), LobbyHandler) as server:
|
||||
server.serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in New Issue