Merge remote-tracking branch 'origin/release-candidate' into release
							
								
								
									
										78
									
								
								.github/actions/submit_sdk/action.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,78 @@
 | 
				
			|||||||
 | 
					name: Submit SDK to Catalog
 | 
				
			||||||
 | 
					author: hedger
 | 
				
			||||||
 | 
					description: |
 | 
				
			||||||
 | 
					  This action checks if SDK exists in the catalog and if not, adds and/or publishes it.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					inputs:
 | 
				
			||||||
 | 
					  catalog-url:
 | 
				
			||||||
 | 
					    description: The URL of the Catalog API. Must not be empty or end with a /.
 | 
				
			||||||
 | 
					    required: true
 | 
				
			||||||
 | 
					  catalog-api-token:
 | 
				
			||||||
 | 
					    description: The token to use to authenticate with the Catalog API. Must not be empty.
 | 
				
			||||||
 | 
					    required: true
 | 
				
			||||||
 | 
					  firmware-api:
 | 
				
			||||||
 | 
					    description: Fimware's API version, major.minor
 | 
				
			||||||
 | 
					    required: true
 | 
				
			||||||
 | 
					  firmware-target:
 | 
				
			||||||
 | 
					    description: Firmware's target, e.g. f7/f18
 | 
				
			||||||
 | 
					    required: true
 | 
				
			||||||
 | 
					  firmware-version:
 | 
				
			||||||
 | 
					    description: Firmware's version, e.g. 0.13.37-rc3, or 0.13.37
 | 
				
			||||||
 | 
					    required: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					runs:
 | 
				
			||||||
 | 
					  using: composite
 | 
				
			||||||
 | 
					  steps:
 | 
				
			||||||
 | 
					    - name: Check inputs
 | 
				
			||||||
 | 
					      shell: bash
 | 
				
			||||||
 | 
					      run: |
 | 
				
			||||||
 | 
					        if [ -z "${{ inputs.catalog-url }}" ] ; then
 | 
				
			||||||
 | 
					          echo "Invalid catalog-url: ${{ inputs.catalog-url }}"
 | 
				
			||||||
 | 
					          exit 1
 | 
				
			||||||
 | 
					        fi
 | 
				
			||||||
 | 
					        if [ -z "${{ inputs.catalog-api-token }}" ] ; then
 | 
				
			||||||
 | 
					          echo "Invalid catalog-api-token: ${{ inputs.catalog-api-token }}"
 | 
				
			||||||
 | 
					          exit 1
 | 
				
			||||||
 | 
					        fi
 | 
				
			||||||
 | 
					        if ! echo "${{ inputs.firmware-api }}" | grep -q "^[0-9]\+\.[0-9]\+$" ; then
 | 
				
			||||||
 | 
					          echo "Invalid firmware-api: ${{ inputs.firmware-api }}"
 | 
				
			||||||
 | 
					          exit 1
 | 
				
			||||||
 | 
					        fi
 | 
				
			||||||
 | 
					        if ! echo "${{ inputs.firmware-target }}" | grep -q "^f[0-9]\+$" ; then
 | 
				
			||||||
 | 
					          echo "Invalid firmware-target: ${{ inputs.firmware-target }}"
 | 
				
			||||||
 | 
					          exit 1
 | 
				
			||||||
 | 
					        fi
 | 
				
			||||||
 | 
					        if ! echo "${{ inputs.firmware-version }}" | grep -q "^[0-9]\+\.[0-9]\+\.[0-9]\+\(-rc\)\?\([0-9]\+\)\?$" ; then
 | 
				
			||||||
 | 
					          echo "Invalid firmware-version: ${{ inputs.firmware-version }}"
 | 
				
			||||||
 | 
					          exit 1
 | 
				
			||||||
 | 
					        fi
 | 
				
			||||||
 | 
					    - name: Submit SDK
 | 
				
			||||||
 | 
					      shell: bash
 | 
				
			||||||
 | 
					      run: |
 | 
				
			||||||
 | 
					        curl -sX 'GET' \
 | 
				
			||||||
 | 
					          '${{ inputs.catalog-url }}/api/v0/0/sdk?length=500' \
 | 
				
			||||||
 | 
					          -H 'Accept: application/json' > sdk_versions.json
 | 
				
			||||||
 | 
					        if jq -r -e ".[] | select((.api == \"${{ inputs.firmware-api }}\") and .target == \"${{ inputs.firmware-target }}\")" sdk_versions.json > found_sdk.json ; then
 | 
				
			||||||
 | 
					          echo "API version ${{ inputs.firmware-api }} already exists in catalog"
 | 
				
			||||||
 | 
					          if [ $(jq -r -e ".released_at" found_sdk.json) != "null" ] ; then
 | 
				
			||||||
 | 
					            echo "API version is already released"
 | 
				
			||||||
 | 
					            exit 0
 | 
				
			||||||
 | 
					          fi
 | 
				
			||||||
 | 
					          if ! echo "${{ inputs.firmware-version }}" | grep -q "-rc" ; then
 | 
				
			||||||
 | 
					            SDK_ID=$(jq -r ._id found_sdk.json)
 | 
				
			||||||
 | 
					            echo "Marking SDK $SDK_ID as released"
 | 
				
			||||||
 | 
					            curl -X 'POST' \
 | 
				
			||||||
 | 
					              "${{ inputs.catalog-url }}/api/v0/0/sdk/${SDK_ID}/release" \
 | 
				
			||||||
 | 
					              -H 'Accept: application/json' \
 | 
				
			||||||
 | 
					              -H 'Authorization: Bearer ${{ inputs.catalog-api-token }}' \
 | 
				
			||||||
 | 
					              -d ''
 | 
				
			||||||
 | 
					          fi
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					          echo "API version ${{ inputs.firmware-api }} doesn't exist in catalog, adding"
 | 
				
			||||||
 | 
					          curl -X 'POST' \
 | 
				
			||||||
 | 
					            '${{ inputs.catalog-url }}/api/v0/0/sdk' \
 | 
				
			||||||
 | 
					            -H 'Accept: application/json' \
 | 
				
			||||||
 | 
					            -H 'Authorization: Bearer ${{ inputs.catalog-api-token }}' \
 | 
				
			||||||
 | 
					            -H 'Content-Type: application/json' \
 | 
				
			||||||
 | 
					            -d "{\"name\": \"${{ inputs.firmware-version }}\", \"target\": \"${{ inputs.firmware-target }}\", \"api\": \"${{ inputs.firmware-api }}\"}"
 | 
				
			||||||
 | 
					        fi
 | 
				
			||||||
							
								
								
									
										167
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						@ -9,13 +9,16 @@ on:
 | 
				
			|||||||
  pull_request:
 | 
					  pull_request:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
env:
 | 
					env:
 | 
				
			||||||
  TARGETS: f7 f18
 | 
					 | 
				
			||||||
  DEFAULT_TARGET: f7
 | 
					  DEFAULT_TARGET: f7
 | 
				
			||||||
  FBT_TOOLCHAIN_PATH: /runner/_work
 | 
					  FBT_TOOLCHAIN_PATH: /runner/_work
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  main:
 | 
					  main:
 | 
				
			||||||
    runs-on: [self-hosted, FlipperZeroShell]
 | 
					    runs-on: [self-hosted, FlipperZeroShell]
 | 
				
			||||||
 | 
					    strategy:
 | 
				
			||||||
 | 
					      fail-fast: false
 | 
				
			||||||
 | 
					      matrix:
 | 
				
			||||||
 | 
					        target: [f7, f18]    
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: 'Wipe workspace'
 | 
					      - name: 'Wipe workspace'
 | 
				
			||||||
        run: find ./ -mount -maxdepth 1 -exec rm -rf {} \;
 | 
					        run: find ./ -mount -maxdepth 1 -exec rm -rf {} \;
 | 
				
			||||||
@ -29,73 +32,77 @@ jobs:
 | 
				
			|||||||
      - name: 'Get commit details'
 | 
					      - name: 'Get commit details'
 | 
				
			||||||
        id: names
 | 
					        id: names
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
 | 
					          BUILD_TYPE='DEBUG=1 COMPACT=0'
 | 
				
			||||||
          if [[ ${{ github.event_name }} == 'pull_request' ]]; then
 | 
					          if [[ ${{ github.event_name }} == 'pull_request' ]]; then
 | 
				
			||||||
            TYPE="pull"
 | 
					            TYPE="pull"
 | 
				
			||||||
          elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
 | 
					          elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
 | 
				
			||||||
            TYPE="tag"
 | 
					            TYPE="tag"
 | 
				
			||||||
 | 
					            BUILD_TYPE='DEBUG=0 COMPACT=1'
 | 
				
			||||||
          else
 | 
					          else
 | 
				
			||||||
            TYPE="other"
 | 
					            TYPE="other"
 | 
				
			||||||
          fi
 | 
					          fi
 | 
				
			||||||
          python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" || cat "${{ github.event_path }}"
 | 
					          python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" || cat "${{ github.event_path }}"
 | 
				
			||||||
          echo random_hash=$(openssl rand -base64 40 | shasum -a 256 | awk '{print $1}') >> $GITHUB_OUTPUT
 | 
					 | 
				
			||||||
          echo "event_type=$TYPE" >> $GITHUB_OUTPUT
 | 
					          echo "event_type=$TYPE" >> $GITHUB_OUTPUT
 | 
				
			||||||
 | 
					          echo "FBT_BUILD_TYPE=$BUILD_TYPE" >> $GITHUB_ENV
 | 
				
			||||||
 | 
					          echo "TARGET=${{ matrix.target }}" >> $GITHUB_ENV
 | 
				
			||||||
 | 
					          echo "TARGET_HW=$(echo "${{ matrix.target }}" | sed 's/f//')" >> $GITHUB_ENV
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: 'Check API versions'
 | 
					      - name: 'Check API versions for consistency between targets'
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          set -e
 | 
					          set -e
 | 
				
			||||||
          N_API_HEADER_SIGNATURES=`ls -1 firmware/targets/f*/api_symbols.csv | xargs -I {} sh -c "head -n2 {} | md5sum" | sort -u | wc -l`
 | 
					          N_API_HEADER_SIGNATURES=`ls -1 firmware/targets/f*/api_symbols.csv | xargs -I {} sh -c "head -n2 {} | md5sum" | sort -u | wc -l`
 | 
				
			||||||
          if [ $N_API_HEADER_SIGNATURES != 1 ] ; then
 | 
					          if [ $N_API_HEADER_SIGNATURES != 1 ] ; then
 | 
				
			||||||
            echo API versions aren\'t matching for available targets. Please update!
 | 
					            echo API versions aren\'t matching for available targets. Please update!
 | 
				
			||||||
 | 
					            echo API versions are:
 | 
				
			||||||
            head -n2 firmware/targets/f*/api_symbols.csv
 | 
					            head -n2 firmware/targets/f*/api_symbols.csv
 | 
				
			||||||
            exit 1
 | 
					            exit 1
 | 
				
			||||||
          fi
 | 
					          fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: 'Make artifacts directory'
 | 
					      - name: 'Build the firmware and apps'
 | 
				
			||||||
 | 
					        id: build-fw
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          rm -rf artifacts map_analyser_files
 | 
					          ./fbt TARGET_HW=$TARGET_HW $FBT_BUILD_TYPE copro_dist updater_package fap_dist
 | 
				
			||||||
          mkdir artifacts map_analyser_files
 | 
					          echo "firmware_api=$(./fbt TARGET_HW=$TARGET_HW get_apiversion)" >> $GITHUB_OUTPUT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: 'Bundle scripts'
 | 
					      - name: 'Check for uncommitted changes'
 | 
				
			||||||
        if: ${{ !github.event.pull_request.head.repo.fork }}
 | 
					 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          tar czpf "artifacts/flipper-z-any-scripts-${SUFFIX}.tgz" scripts
 | 
					          git diff --exit-code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: 'Build the firmware'
 | 
					      - name: 'Copy build output'
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          set -e
 | 
					          set -e
 | 
				
			||||||
          for TARGET in ${TARGETS}; do
 | 
					          rm -rf artifacts map_analyser_files || true
 | 
				
			||||||
            TARGET_HW="$(echo "${TARGET}" | sed 's/f//')"; \
 | 
					          mkdir artifacts map_analyser_files
 | 
				
			||||||
            ./fbt TARGET_HW=$TARGET_HW copro_dist updater_package \
 | 
					          cp dist/${TARGET}-*/* artifacts/ || true
 | 
				
			||||||
              ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }}
 | 
					 | 
				
			||||||
            mv dist/${TARGET}-*/* artifacts/
 | 
					 | 
				
			||||||
          tar czpf "artifacts/flipper-z-${TARGET}-resources-${SUFFIX}.tgz" \
 | 
					          tar czpf "artifacts/flipper-z-${TARGET}-resources-${SUFFIX}.tgz" \
 | 
				
			||||||
            -C assets resources
 | 
					            -C assets resources
 | 
				
			||||||
            ./fbt TARGET_HW=$TARGET_HW fap_dist
 | 
					 | 
				
			||||||
          tar czpf "artifacts/flipper-z-${TARGET}-debugapps-${SUFFIX}.tgz" \
 | 
					          tar czpf "artifacts/flipper-z-${TARGET}-debugapps-${SUFFIX}.tgz" \
 | 
				
			||||||
            -C dist/${TARGET}-*/apps/Debug .
 | 
					            -C dist/${TARGET}-*/apps/Debug .
 | 
				
			||||||
          tar czpf "artifacts/flipper-z-${TARGET}-appsymbols-${SUFFIX}.tgz" \
 | 
					          tar czpf "artifacts/flipper-z-${TARGET}-appsymbols-${SUFFIX}.tgz" \
 | 
				
			||||||
            -C dist/${TARGET}-*/debug_elf .
 | 
					            -C dist/${TARGET}-*/debug_elf .
 | 
				
			||||||
          done
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: "Check for uncommitted changes"
 | 
					      - name: 'Copy universal artifacts'
 | 
				
			||||||
        run: |
 | 
					        if: ${{ !github.event.pull_request.head.repo.fork && matrix.target == env.DEFAULT_TARGET }}
 | 
				
			||||||
          git diff --exit-code
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: 'Bundle core2 firmware'
 | 
					 | 
				
			||||||
        if: ${{ !github.event.pull_request.head.repo.fork }}
 | 
					 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
 | 
					          tar czpf "artifacts/flipper-z-any-scripts-${SUFFIX}.tgz" scripts
 | 
				
			||||||
          cp build/core2_firmware.tgz "artifacts/flipper-z-any-core2_firmware-${SUFFIX}.tgz"
 | 
					          cp build/core2_firmware.tgz "artifacts/flipper-z-any-core2_firmware-${SUFFIX}.tgz"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: 'Copy map analyser files'
 | 
					      - name: 'Upload artifacts to update server'
 | 
				
			||||||
        if: ${{ !github.event.pull_request.head.repo.fork }}
 | 
					        if: ${{ !github.event.pull_request.head.repo.fork }}
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          FILES=$(for ARTIFACT in $(find artifacts -maxdepth 1 -not -type d); do echo "-F files=@${ARTIFACT}"; done)
 | 
				
			||||||
 | 
					          curl --fail -L -H "Token: ${{ secrets.INDEXER_TOKEN }}" \
 | 
				
			||||||
 | 
					              -F "branch=${BRANCH_NAME}" \
 | 
				
			||||||
 | 
					              -F "version_token=${COMMIT_SHA}" \
 | 
				
			||||||
 | 
					              ${FILES[@]} \
 | 
				
			||||||
 | 
					              "${{ secrets.INDEXER_URL }}"/firmware/uploadfiles
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: 'Copy & analyse map analyser files'
 | 
				
			||||||
 | 
					        if: ${{ !github.event.pull_request.head.repo.fork && matrix.target == env.DEFAULT_TARGET }}
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          cp build/${DEFAULT_TARGET}-firmware-*/firmware.elf.map map_analyser_files/firmware.elf.map
 | 
					          cp build/${DEFAULT_TARGET}-firmware-*/firmware.elf.map map_analyser_files/firmware.elf.map
 | 
				
			||||||
          cp build/${DEFAULT_TARGET}-firmware-*/firmware.elf map_analyser_files/firmware.elf
 | 
					          cp build/${DEFAULT_TARGET}-firmware-*/firmware.elf map_analyser_files/firmware.elf
 | 
				
			||||||
          cp ${{ github.event_path }} map_analyser_files/event.json
 | 
					          cp ${{ github.event_path }} map_analyser_files/event.json
 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: 'Analyse map file'
 | 
					 | 
				
			||||||
        if: ${{ !github.event.pull_request.head.repo.fork }}
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          source scripts/toolchain/fbtenv.sh
 | 
					          source scripts/toolchain/fbtenv.sh
 | 
				
			||||||
          get_size()
 | 
					          get_size()
 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
@ -119,102 +126,44 @@ jobs:
 | 
				
			|||||||
              ${{ secrets.AMAP_MARIADB_DATABASE }} \
 | 
					              ${{ secrets.AMAP_MARIADB_DATABASE }} \
 | 
				
			||||||
              map_analyser_files/firmware.elf.map.all
 | 
					              map_analyser_files/firmware.elf.map.all
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: 'Upload artifacts to update server'
 | 
					      - name: 'Find previous comment'
 | 
				
			||||||
        if: ${{ !github.event.pull_request.head.repo.fork }}
 | 
					        if: ${{ !github.event.pull_request.head.repo.fork && matrix.target == env.DEFAULT_TARGET && github.event.pull_request }}
 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          FILES=$(for CUR in $(ls artifacts/); do echo "-F files=@artifacts/$CUR"; done)
 | 
					 | 
				
			||||||
          curl --fail -L -H "Token: ${{ secrets.INDEXER_TOKEN }}" \
 | 
					 | 
				
			||||||
              -F "branch=${BRANCH_NAME}" \
 | 
					 | 
				
			||||||
              ${FILES[@]} \
 | 
					 | 
				
			||||||
              "${{ secrets.INDEXER_URL }}"/firmware/uploadfiles
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: 'Find Previous Comment'
 | 
					 | 
				
			||||||
        if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request }}
 | 
					 | 
				
			||||||
        uses: peter-evans/find-comment@v2
 | 
					        uses: peter-evans/find-comment@v2
 | 
				
			||||||
        id: fc
 | 
					        id: find-comment
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          issue-number: ${{ github.event.pull_request.number }}
 | 
					          issue-number: ${{ github.event.pull_request.number }}
 | 
				
			||||||
          comment-author: 'github-actions[bot]'
 | 
					          comment-author: 'github-actions[bot]'
 | 
				
			||||||
          body-includes: 'Compiled firmware for commit'
 | 
					          body-includes: 'Compiled ${{ matrix.target }} firmware for commit'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: 'Create or update comment'
 | 
					      - name: 'Create or update comment'
 | 
				
			||||||
        if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request}}
 | 
					        if: ${{ !github.event.pull_request.head.repo.fork && matrix.target == env.DEFAULT_TARGET && github.event.pull_request }}
 | 
				
			||||||
        uses: peter-evans/create-or-update-comment@v3
 | 
					        uses: peter-evans/create-or-update-comment@v3
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          comment-id: ${{ steps.fc.outputs.comment-id }}
 | 
					          comment-id: ${{ steps.find-comment.outputs.comment-id }}
 | 
				
			||||||
          issue-number: ${{ github.event.pull_request.number }}
 | 
					          issue-number: ${{ github.event.pull_request.number }}
 | 
				
			||||||
          body: |
 | 
					          body: |
 | 
				
			||||||
            **Compiled firmware for commit `${{steps.names.outputs.commit_sha}}`:**
 | 
					            **Compiled ${{ matrix.target }} firmware for commit `${{steps.names.outputs.commit_sha}}`:**
 | 
				
			||||||
            - [📦 Update package](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz)
 | 
					            - [📦 Update package](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz)
 | 
				
			||||||
            - [📥 DFU file](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-full-${{steps.names.outputs.suffix}}.dfu)
 | 
					            - [📥 DFU file](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-full-${{steps.names.outputs.suffix}}.dfu)
 | 
				
			||||||
            - [☁️ Web/App updater](https://lab.flipper.net/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.branch_name}}&version=${{steps.names.outputs.commit_sha}})
 | 
					            - [☁️ Web/App updater](https://lab.flipper.net/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.branch_name}}&version=${{steps.names.outputs.commit_sha}})
 | 
				
			||||||
          edit-mode: replace
 | 
					          edit-mode: replace
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  compact:
 | 
					      - name: 'SDK submission to staging catalog'
 | 
				
			||||||
    if: ${{ !startsWith(github.ref, 'refs/tags') }}
 | 
					        if: ${{ steps.names.outputs.event_type == 'tag' && matrix.target == env.DEFAULT_TARGET }}
 | 
				
			||||||
    runs-on: [self-hosted,FlipperZeroShell]
 | 
					        uses: ./.github/actions/submit_sdk
 | 
				
			||||||
    strategy:
 | 
					 | 
				
			||||||
      matrix:
 | 
					 | 
				
			||||||
        target: [f7, f18]
 | 
					 | 
				
			||||||
    steps:
 | 
					 | 
				
			||||||
      - name: 'Wipe workspace'
 | 
					 | 
				
			||||||
        run: find ./ -mount -maxdepth 1 -exec rm -rf {} \;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: 'Checkout code'
 | 
					 | 
				
			||||||
        uses: actions/checkout@v3
 | 
					 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          fetch-depth: 1
 | 
					          catalog-url: ${{ secrets.CATALOG_STAGING_URL }}
 | 
				
			||||||
          submodules: false
 | 
					          catalog-api-token: ${{ secrets.CATALOG_STAGING_API_TOKEN }}
 | 
				
			||||||
          ref: ${{ github.event.pull_request.head.sha }}
 | 
					          firmware-api: ${{ steps.build-fw.outputs.firmware_api }}
 | 
				
			||||||
 | 
					          firmware-target: ${{ matrix.target }}
 | 
				
			||||||
 | 
					          firmware-version: ${{ steps.names.outputs.suffix }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: 'Get commit details'
 | 
					      - name: 'SDK submission to prod catalog'
 | 
				
			||||||
        run: |
 | 
					        if: ${{ steps.names.outputs.event_type == 'tag' && matrix.target == env.DEFAULT_TARGET }}
 | 
				
			||||||
          if [[ ${{ github.event_name }} == 'pull_request' ]]; then
 | 
					        uses: ./.github/actions/submit_sdk
 | 
				
			||||||
            TYPE="pull"
 | 
					 | 
				
			||||||
          elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
 | 
					 | 
				
			||||||
            TYPE="tag"
 | 
					 | 
				
			||||||
          else
 | 
					 | 
				
			||||||
            TYPE="other"
 | 
					 | 
				
			||||||
          fi
 | 
					 | 
				
			||||||
          python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" || cat "${{ github.event_path }}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: 'Build the firmware'
 | 
					 | 
				
			||||||
        id: build-fw
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          set -e
 | 
					 | 
				
			||||||
          TARGET="$(echo '${{ matrix.target }}' | sed 's/f//')"; \
 | 
					 | 
				
			||||||
          ./fbt TARGET_HW=$TARGET DEBUG=0 COMPACT=1 fap_dist updater_package
 | 
					 | 
				
			||||||
          echo "sdk-file=$(ls dist/${{ matrix.target }}-*/flipper-z-${{ matrix.target }}-sdk-*.zip)" >> $GITHUB_OUTPUT
 | 
					 | 
				
			||||||
          echo "hw-target-code=$TARGET" >> $GITHUB_OUTPUT
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Deploy uFBT with SDK
 | 
					 | 
				
			||||||
        uses: flipperdevices/flipperzero-ufbt-action@v0.1.0
 | 
					 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          task: setup
 | 
					          catalog-url: ${{ secrets.CATALOG_URL }}
 | 
				
			||||||
          sdk-file: ${{ steps.build-fw.outputs.sdk-file }}
 | 
					          catalog-api-token: ${{ secrets.CATALOG_API_TOKEN }}
 | 
				
			||||||
          sdk-hw-target: ${{ steps.build-fw.outputs.hw-target-code }}
 | 
					          firmware-api: ${{ steps.build-fw.outputs.firmware_api }}
 | 
				
			||||||
 | 
					          firmware-target: ${{ matrix.target }}
 | 
				
			||||||
      - name: Build test app with SDK
 | 
					          firmware-version: ${{ steps.names.outputs.suffix }}
 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          mkdir testapp
 | 
					 | 
				
			||||||
          cd testapp
 | 
					 | 
				
			||||||
          ufbt create APPID=testapp
 | 
					 | 
				
			||||||
          ufbt
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Build example & external apps with uFBT
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          for appdir in 'applications/examples'; do
 | 
					 | 
				
			||||||
            for app in $(find "$appdir" -maxdepth 1 -mindepth 1 -type d); do
 | 
					 | 
				
			||||||
              pushd $app
 | 
					 | 
				
			||||||
              TARGETS_FAM=$(grep "targets" application.fam || echo "${{ matrix.target }}")
 | 
					 | 
				
			||||||
              if ! grep -q "${{ matrix.target }}" <<< $TARGETS_FAM ; then
 | 
					 | 
				
			||||||
                  echo Skipping unsupported app: $app
 | 
					 | 
				
			||||||
                  popd
 | 
					 | 
				
			||||||
                  continue
 | 
					 | 
				
			||||||
              fi
 | 
					 | 
				
			||||||
              echo Building $app
 | 
					 | 
				
			||||||
              ufbt
 | 
					 | 
				
			||||||
              popd
 | 
					 | 
				
			||||||
            done
 | 
					 | 
				
			||||||
          done
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										85
									
								
								.github/workflows/build_compact.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,85 @@
 | 
				
			|||||||
 | 
					name: 'Compact build'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					on:
 | 
				
			||||||
 | 
					  pull_request:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					env:
 | 
				
			||||||
 | 
					  FBT_TOOLCHAIN_PATH: /runner/_work
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					  compact:
 | 
				
			||||||
 | 
					    runs-on: [self-hosted, FlipperZeroShell]
 | 
				
			||||||
 | 
					    strategy:
 | 
				
			||||||
 | 
					      fail-fast: false
 | 
				
			||||||
 | 
					      matrix:
 | 
				
			||||||
 | 
					        target: [f7, f18]
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - name: 'Wipe workspace'
 | 
				
			||||||
 | 
					        run: find ./ -mount -maxdepth 1 -exec rm -rf {} \;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: 'Checkout code'
 | 
				
			||||||
 | 
					        uses: actions/checkout@v3
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          fetch-depth: 1
 | 
				
			||||||
 | 
					          submodules: false
 | 
				
			||||||
 | 
					          ref: ${{ github.event.pull_request.head.sha }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: 'Get commit details'
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          if [[ ${{ github.event_name }} == 'pull_request' ]]; then
 | 
				
			||||||
 | 
					            TYPE="pull"
 | 
				
			||||||
 | 
					          elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
 | 
				
			||||||
 | 
					            TYPE="tag"
 | 
				
			||||||
 | 
					          else
 | 
				
			||||||
 | 
					            TYPE="other"
 | 
				
			||||||
 | 
					          fi
 | 
				
			||||||
 | 
					          python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" || cat "${{ github.event_path }}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: 'Build the firmware'
 | 
				
			||||||
 | 
					        id: build-fw
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          set -e
 | 
				
			||||||
 | 
					          TARGET="$(echo '${{ matrix.target }}' | sed 's/f//')"; \
 | 
				
			||||||
 | 
					          ./fbt TARGET_HW=$TARGET DEBUG=0 COMPACT=1 fap_dist updater_package
 | 
				
			||||||
 | 
					          echo "sdk-file=$(ls dist/${{ matrix.target }}-*/flipper-z-${{ matrix.target }}-sdk-*.zip)" >> $GITHUB_OUTPUT
 | 
				
			||||||
 | 
					          echo "hw-target-code=$TARGET" >> $GITHUB_OUTPUT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Deploy uFBT with SDK
 | 
				
			||||||
 | 
					        uses: flipperdevices/flipperzero-ufbt-action@v0.1.0
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          task: setup
 | 
				
			||||||
 | 
					          sdk-file: ${{ steps.build-fw.outputs.sdk-file }}
 | 
				
			||||||
 | 
					          sdk-hw-target: ${{ steps.build-fw.outputs.hw-target-code }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Build test app with SDK
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          mkdir testapp
 | 
				
			||||||
 | 
					          cd testapp
 | 
				
			||||||
 | 
					          ufbt create APPID=testapp
 | 
				
			||||||
 | 
					          ufbt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Build example & external apps with uFBT
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          for appdir in 'applications/examples'; do
 | 
				
			||||||
 | 
					            for app in $(find "$appdir" -maxdepth 1 -mindepth 1 -type d); do
 | 
				
			||||||
 | 
					              pushd $app
 | 
				
			||||||
 | 
					              TARGETS_FAM=$(grep "targets" application.fam || echo "${{ matrix.target }}")
 | 
				
			||||||
 | 
					              if ! grep -q "${{ matrix.target }}" <<< $TARGETS_FAM ; then
 | 
				
			||||||
 | 
					                  echo Skipping unsupported app: $app
 | 
				
			||||||
 | 
					                  popd
 | 
				
			||||||
 | 
					                  continue
 | 
				
			||||||
 | 
					              fi
 | 
				
			||||||
 | 
					              echo Building $app
 | 
				
			||||||
 | 
					              ufbt
 | 
				
			||||||
 | 
					              popd
 | 
				
			||||||
 | 
					            done
 | 
				
			||||||
 | 
					          done
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Uncomment this for a single job that will run only if all targets are built successfully          
 | 
				
			||||||
 | 
					#  report-status:
 | 
				
			||||||
 | 
					#    name: Report status
 | 
				
			||||||
 | 
					#    needs: [compact]
 | 
				
			||||||
 | 
					#    if: always() && !contains(needs.*.result, 'failure')
 | 
				
			||||||
 | 
					#    runs-on: [self-hosted, FlipperZeroShell]
 | 
				
			||||||
 | 
					#    steps:
 | 
				
			||||||
 | 
					#      - run: echo "All good ✨" ;
 | 
				
			||||||
@ -1,11 +1,6 @@
 | 
				
			|||||||
name: 'Lint sources & check submodule integrity'
 | 
					name: 'Lint sources & check submodule integrity'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
on:
 | 
					on:
 | 
				
			||||||
  push:
 | 
					 | 
				
			||||||
    branches:
 | 
					 | 
				
			||||||
      - dev
 | 
					 | 
				
			||||||
    tags:
 | 
					 | 
				
			||||||
      - '*'
 | 
					 | 
				
			||||||
  pull_request:
 | 
					  pull_request:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
env:
 | 
					env:
 | 
				
			||||||
@ -53,7 +48,7 @@ jobs:
 | 
				
			|||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          set +e;
 | 
					          set +e;
 | 
				
			||||||
          git diff --unified=0 --no-color ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep -E '^\+' | grep -i -E '(TODO|HACK|FIXME|XXX)[ :]' | grep -v -- '-nofl' > lines.log;
 | 
					          git diff --unified=0 --no-color ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep -E '^\+' | grep -i -E '(TODO|HACK|FIXME|XXX)[ :]' | grep -v -- '-nofl' > lines.log;
 | 
				
			||||||
          MISSING_TICKETS=$( grep -v -E '\[FL-[0-9]+\]' lines.log );
 | 
					          MISSING_TICKETS=$( grep -v -E 'FL-[0-9]+' lines.log );
 | 
				
			||||||
          if [ -n "$MISSING_TICKETS" ]; then
 | 
					          if [ -n "$MISSING_TICKETS" ]; then
 | 
				
			||||||
            echo "Error: Missing ticket number in \`TODO\` comment(s)" >> $GITHUB_STEP_SUMMARY;
 | 
					            echo "Error: Missing ticket number in \`TODO\` comment(s)" >> $GITHUB_STEP_SUMMARY;
 | 
				
			||||||
            echo "\`\`\`" >> $GITHUB_STEP_SUMMARY;
 | 
					            echo "\`\`\`" >> $GITHUB_STEP_SUMMARY;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.github/workflows/pvs_studio.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						@ -4,8 +4,6 @@ on:
 | 
				
			|||||||
  push:
 | 
					  push:
 | 
				
			||||||
    branches:
 | 
					    branches:
 | 
				
			||||||
      - dev
 | 
					      - dev
 | 
				
			||||||
    tags:
 | 
					 | 
				
			||||||
      - '*'
 | 
					 | 
				
			||||||
  pull_request:
 | 
					  pull_request:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
env:
 | 
					env:
 | 
				
			||||||
 | 
				
			|||||||
@ -36,6 +36,10 @@ Finally, open a [Pull Request](https://github.com/flipperdevices/flipperzero-fir
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Flipper Zero Firmware is written in C, with some bits and pieces written in C++ and armv7m assembly languages. An intermediate level of C knowledge is recommended for comfortable programming. C, C++, and armv7m assembly languages are supported for Flipper applications.
 | 
					Flipper Zero Firmware is written in C, with some bits and pieces written in C++ and armv7m assembly languages. An intermediate level of C knowledge is recommended for comfortable programming. C, C++, and armv7m assembly languages are supported for Flipper applications.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Firmware RoadMap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Firmware RoadMap Miro Board](https://miro.com/app/board/uXjVO_3D6xU=/)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Requirements
 | 
					## Requirements
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Supported development platforms:
 | 
					Supported development platforms:
 | 
				
			||||||
@ -91,7 +95,6 @@ Make sure your Flipper is on, and your firmware is functioning. Connect your Fli
 | 
				
			|||||||
- [Hardware combos and Un-bricking](/documentation/KeyCombo.md) - recovering your Flipper from the most nasty situations
 | 
					- [Hardware combos and Un-bricking](/documentation/KeyCombo.md) - recovering your Flipper from the most nasty situations
 | 
				
			||||||
- [Flipper File Formats](/documentation/file_formats) - everything about how Flipper stores your data and how you can work with it
 | 
					- [Flipper File Formats](/documentation/file_formats) - everything about how Flipper stores your data and how you can work with it
 | 
				
			||||||
- [Universal Remotes](/documentation/UniversalRemotes.md) - contributing your infrared remote to the universal remote database
 | 
					- [Universal Remotes](/documentation/UniversalRemotes.md) - contributing your infrared remote to the universal remote database
 | 
				
			||||||
- [Firmware Roadmap](/documentation/RoadMap.md)
 | 
					 | 
				
			||||||
- And much more in the [documentation](/documentation) folder
 | 
					- And much more in the [documentation](/documentation) folder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Project structure
 | 
					# Project structure
 | 
				
			||||||
 | 
				
			|||||||
@ -26,7 +26,6 @@ void test_furi_memmgr() {
 | 
				
			|||||||
        mu_assert_int_eq(66, ((uint8_t*)ptr)[i]);
 | 
					        mu_assert_int_eq(66, ((uint8_t*)ptr)[i]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // TODO FL-3492: fix realloc to copy only old size, and write testcase that leftover of reallocated memory is zero-initialized
 | 
					 | 
				
			||||||
    free(ptr);
 | 
					    free(ptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // allocate and zero-initialize array (calloc)
 | 
					    // allocate and zero-initialize array (calloc)
 | 
				
			||||||
 | 
				
			|||||||
@ -67,7 +67,6 @@ static RpcSessionContext rpc_session[TEST_RPC_SESSIONS];
 | 
				
			|||||||
    } while(0)
 | 
					    } while(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void output_bytes_callback(void* ctx, uint8_t* got_bytes, size_t got_size);
 | 
					static void output_bytes_callback(void* ctx, uint8_t* got_bytes, size_t got_size);
 | 
				
			||||||
static void clean_directory(Storage* fs_api, const char* clean_dir);
 | 
					 | 
				
			||||||
static void
 | 
					static void
 | 
				
			||||||
    test_rpc_add_empty_to_list(MsgList_t msg_list, PB_CommandStatus status, uint32_t command_id);
 | 
					    test_rpc_add_empty_to_list(MsgList_t msg_list, PB_CommandStatus status, uint32_t command_id);
 | 
				
			||||||
static void test_rpc_encode_and_feed(MsgList_t msg_list, uint8_t session);
 | 
					static void test_rpc_encode_and_feed(MsgList_t msg_list, uint8_t session);
 | 
				
			||||||
@ -149,11 +148,41 @@ static void test_rpc_teardown_second_session(void) {
 | 
				
			|||||||
    rpc_session[1].session = NULL;
 | 
					    rpc_session[1].session = NULL;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void test_rpc_storage_clean_directory(Storage* fs_api, const char* clean_dir) {
 | 
				
			||||||
 | 
					    furi_check(fs_api);
 | 
				
			||||||
 | 
					    furi_check(clean_dir);
 | 
				
			||||||
 | 
					    storage_simply_remove_recursive(fs_api, clean_dir);
 | 
				
			||||||
 | 
					    FS_Error error = storage_common_mkdir(fs_api, clean_dir);
 | 
				
			||||||
 | 
					    furi_check(error == FSE_OK);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void test_rpc_storage_create_file(Storage* fs_api, const char* path, size_t size) {
 | 
				
			||||||
 | 
					    File* file = storage_file_alloc(fs_api);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool success = false;
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					        if(!storage_file_open(file, path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) break;
 | 
				
			||||||
 | 
					        if(!storage_file_seek(file, size, true)) break;
 | 
				
			||||||
 | 
					        success = true;
 | 
				
			||||||
 | 
					    } while(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    storage_file_close(file);
 | 
				
			||||||
 | 
					    storage_file_free(file);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_check(success);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void test_rpc_storage_setup(void) {
 | 
					static void test_rpc_storage_setup(void) {
 | 
				
			||||||
    test_rpc_setup();
 | 
					    test_rpc_setup();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Storage* fs_api = furi_record_open(RECORD_STORAGE);
 | 
					    Storage* fs_api = furi_record_open(RECORD_STORAGE);
 | 
				
			||||||
    clean_directory(fs_api, TEST_DIR_NAME);
 | 
					    test_rpc_storage_clean_directory(fs_api, TEST_DIR_NAME);
 | 
				
			||||||
 | 
					    test_rpc_storage_create_file(fs_api, TEST_DIR_NAME "/file100", 100);
 | 
				
			||||||
 | 
					    test_rpc_storage_create_file(fs_api, TEST_DIR_NAME "/file250", 250);
 | 
				
			||||||
 | 
					    test_rpc_storage_create_file(fs_api, TEST_DIR_NAME "/file500", 200);
 | 
				
			||||||
 | 
					    test_rpc_storage_create_file(fs_api, TEST_DIR_NAME "/file1000", 1000);
 | 
				
			||||||
 | 
					    test_rpc_storage_create_file(fs_api, TEST_DIR_NAME "/file2500", 2500);
 | 
				
			||||||
 | 
					    test_rpc_storage_create_file(fs_api, TEST_DIR_NAME "/file5000", 5000);
 | 
				
			||||||
    furi_record_close(RECORD_STORAGE);
 | 
					    furi_record_close(RECORD_STORAGE);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -161,7 +190,7 @@ static void test_rpc_storage_teardown(void) {
 | 
				
			|||||||
    test_rpc_teardown();
 | 
					    test_rpc_teardown();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Storage* fs_api = furi_record_open(RECORD_STORAGE);
 | 
					    Storage* fs_api = furi_record_open(RECORD_STORAGE);
 | 
				
			||||||
    clean_directory(fs_api, TEST_DIR_NAME);
 | 
					    test_rpc_storage_clean_directory(fs_api, TEST_DIR_NAME);
 | 
				
			||||||
    furi_record_close(RECORD_STORAGE);
 | 
					    furi_record_close(RECORD_STORAGE);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -179,36 +208,6 @@ static void test_rpc_session_terminated_callback(void* context) {
 | 
				
			|||||||
    xSemaphoreGive(callbacks_context->terminate_semaphore);
 | 
					    xSemaphoreGive(callbacks_context->terminate_semaphore);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void clean_directory(Storage* fs_api, const char* clean_dir) {
 | 
					 | 
				
			||||||
    furi_check(fs_api);
 | 
					 | 
				
			||||||
    furi_check(clean_dir);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    File* dir = storage_file_alloc(fs_api);
 | 
					 | 
				
			||||||
    if(storage_dir_open(dir, clean_dir)) {
 | 
					 | 
				
			||||||
        FileInfo fileinfo;
 | 
					 | 
				
			||||||
        char* name = malloc(MAX_NAME_LENGTH + 1);
 | 
					 | 
				
			||||||
        while(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) {
 | 
					 | 
				
			||||||
            size_t size = strlen(clean_dir) + strlen(name) + 1 + 1;
 | 
					 | 
				
			||||||
            char* fullname = malloc(size);
 | 
					 | 
				
			||||||
            snprintf(fullname, size, "%s/%s", clean_dir, name);
 | 
					 | 
				
			||||||
            if(file_info_is_dir(&fileinfo)) {
 | 
					 | 
				
			||||||
                clean_directory(fs_api, fullname);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            FS_Error error = storage_common_remove(fs_api, fullname);
 | 
					 | 
				
			||||||
            furi_check(error == FSE_OK);
 | 
					 | 
				
			||||||
            free(fullname);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        free(name);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        FS_Error error = storage_common_mkdir(fs_api, clean_dir);
 | 
					 | 
				
			||||||
        (void)error;
 | 
					 | 
				
			||||||
        furi_check(error == FSE_OK);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    storage_dir_close(dir);
 | 
					 | 
				
			||||||
    storage_file_free(dir);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void test_rpc_print_message_list(MsgList_t msg_list) {
 | 
					static void test_rpc_print_message_list(MsgList_t msg_list) {
 | 
				
			||||||
#if DEBUG_PRINT
 | 
					#if DEBUG_PRINT
 | 
				
			||||||
    MsgList_reverse(msg_list);
 | 
					    MsgList_reverse(msg_list);
 | 
				
			||||||
@ -282,24 +281,40 @@ static void test_rpc_add_ping_to_list(MsgList_t msg_list, bool request, uint32_t
 | 
				
			|||||||
    response->which_content = (request == PING_REQUEST) ? PB_Main_system_ping_request_tag :
 | 
					    response->which_content = (request == PING_REQUEST) ? PB_Main_system_ping_request_tag :
 | 
				
			||||||
                                                          PB_Main_system_ping_response_tag;
 | 
					                                                          PB_Main_system_ping_response_tag;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					static void test_rpc_fill_basic_message(PB_Main* message, uint16_t tag, uint32_t command_id) {
 | 
				
			||||||
 | 
					    message->command_id = command_id;
 | 
				
			||||||
 | 
					    message->command_status = PB_CommandStatus_OK;
 | 
				
			||||||
 | 
					    message->cb_content.funcs.encode = NULL;
 | 
				
			||||||
 | 
					    message->which_content = tag;
 | 
				
			||||||
 | 
					    message->has_next = false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void test_rpc_create_storage_list_request(
 | 
				
			||||||
 | 
					    PB_Main* message,
 | 
				
			||||||
 | 
					    const char* path,
 | 
				
			||||||
 | 
					    bool include_md5,
 | 
				
			||||||
 | 
					    uint32_t command_id,
 | 
				
			||||||
 | 
					    uint32_t filter_max_size) {
 | 
				
			||||||
 | 
					    furi_check(message);
 | 
				
			||||||
 | 
					    furi_check(path);
 | 
				
			||||||
 | 
					    test_rpc_fill_basic_message(message, PB_Main_storage_list_request_tag, command_id);
 | 
				
			||||||
 | 
					    message->content.storage_list_request.path = strdup(path);
 | 
				
			||||||
 | 
					    message->content.storage_list_request.include_md5 = include_md5;
 | 
				
			||||||
 | 
					    message->content.storage_list_request.filter_max_size = filter_max_size;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void test_rpc_create_simple_message(
 | 
					static void test_rpc_create_simple_message(
 | 
				
			||||||
    PB_Main* message,
 | 
					    PB_Main* message,
 | 
				
			||||||
    uint16_t tag,
 | 
					    uint16_t tag,
 | 
				
			||||||
    const char* str,
 | 
					    const char* str,
 | 
				
			||||||
    uint32_t command_id,
 | 
					    uint32_t command_id) {
 | 
				
			||||||
    bool flag) {
 | 
					 | 
				
			||||||
    furi_check(message);
 | 
					    furi_check(message);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    char* str_copy = NULL;
 | 
					    char* str_copy = NULL;
 | 
				
			||||||
    if(str) {
 | 
					    if(str) {
 | 
				
			||||||
        str_copy = strdup(str);
 | 
					        str_copy = strdup(str);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    message->command_id = command_id;
 | 
					    test_rpc_fill_basic_message(message, tag, command_id);
 | 
				
			||||||
    message->command_status = PB_CommandStatus_OK;
 | 
					 | 
				
			||||||
    message->cb_content.funcs.encode = NULL;
 | 
					 | 
				
			||||||
    message->which_content = tag;
 | 
					 | 
				
			||||||
    message->has_next = false;
 | 
					 | 
				
			||||||
    switch(tag) {
 | 
					    switch(tag) {
 | 
				
			||||||
    case PB_Main_storage_info_request_tag:
 | 
					    case PB_Main_storage_info_request_tag:
 | 
				
			||||||
        message->content.storage_info_request.path = str_copy;
 | 
					        message->content.storage_info_request.path = str_copy;
 | 
				
			||||||
@ -307,10 +322,6 @@ static void test_rpc_create_simple_message(
 | 
				
			|||||||
    case PB_Main_storage_stat_request_tag:
 | 
					    case PB_Main_storage_stat_request_tag:
 | 
				
			||||||
        message->content.storage_stat_request.path = str_copy;
 | 
					        message->content.storage_stat_request.path = str_copy;
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
    case PB_Main_storage_list_request_tag:
 | 
					 | 
				
			||||||
        message->content.storage_list_request.path = str_copy;
 | 
					 | 
				
			||||||
        message->content.storage_list_request.include_md5 = flag;
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
    case PB_Main_storage_mkdir_request_tag:
 | 
					    case PB_Main_storage_mkdir_request_tag:
 | 
				
			||||||
        message->content.storage_mkdir_request.path = str_copy;
 | 
					        message->content.storage_mkdir_request.path = str_copy;
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
@ -573,11 +584,29 @@ static void
 | 
				
			|||||||
    message->content.storage_list_response.file[2].name = str;
 | 
					    message->content.storage_list_response.file[2].name = str;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool test_rpc_system_storage_list_filter(
 | 
				
			||||||
 | 
					    const FileInfo* fileinfo,
 | 
				
			||||||
 | 
					    const char* name,
 | 
				
			||||||
 | 
					    size_t filter_max_size) {
 | 
				
			||||||
 | 
					    bool result = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					        if(!path_contains_only_ascii(name)) break;
 | 
				
			||||||
 | 
					        if(filter_max_size) {
 | 
				
			||||||
 | 
					            if(fileinfo->size > filter_max_size) break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        result = true;
 | 
				
			||||||
 | 
					    } while(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void test_rpc_storage_list_create_expected_list(
 | 
					static void test_rpc_storage_list_create_expected_list(
 | 
				
			||||||
    MsgList_t msg_list,
 | 
					    MsgList_t msg_list,
 | 
				
			||||||
    const char* path,
 | 
					    const char* path,
 | 
				
			||||||
    uint32_t command_id,
 | 
					    uint32_t command_id,
 | 
				
			||||||
    bool append_md5) {
 | 
					    bool append_md5,
 | 
				
			||||||
 | 
					    size_t filter_max_size) {
 | 
				
			||||||
    Storage* fs_api = furi_record_open(RECORD_STORAGE);
 | 
					    Storage* fs_api = furi_record_open(RECORD_STORAGE);
 | 
				
			||||||
    File* dir = storage_file_alloc(fs_api);
 | 
					    File* dir = storage_file_alloc(fs_api);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -615,7 +644,7 @@ static void test_rpc_storage_list_create_expected_list(
 | 
				
			|||||||
                i = 0;
 | 
					                i = 0;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if(path_contains_only_ascii(name)) {
 | 
					            if(test_rpc_system_storage_list_filter(&fileinfo, name, filter_max_size)) {
 | 
				
			||||||
                list->file[i].type = file_info_is_dir(&fileinfo) ? PB_Storage_File_FileType_DIR :
 | 
					                list->file[i].type = file_info_is_dir(&fileinfo) ? PB_Storage_File_FileType_DIR :
 | 
				
			||||||
                                                                   PB_Storage_File_FileType_FILE;
 | 
					                                                                   PB_Storage_File_FileType_FILE;
 | 
				
			||||||
                list->file[i].size = fileinfo.size;
 | 
					                list->file[i].size = fileinfo.size;
 | 
				
			||||||
@ -698,17 +727,21 @@ static void test_rpc_free_msg_list(MsgList_t msg_list) {
 | 
				
			|||||||
    MsgList_clear(msg_list);
 | 
					    MsgList_clear(msg_list);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void test_rpc_storage_list_run(const char* path, uint32_t command_id, bool md5) {
 | 
					static void test_rpc_storage_list_run(
 | 
				
			||||||
 | 
					    const char* path,
 | 
				
			||||||
 | 
					    uint32_t command_id,
 | 
				
			||||||
 | 
					    bool md5,
 | 
				
			||||||
 | 
					    size_t filter_max_size) {
 | 
				
			||||||
    PB_Main request;
 | 
					    PB_Main request;
 | 
				
			||||||
    MsgList_t expected_msg_list;
 | 
					    MsgList_t expected_msg_list;
 | 
				
			||||||
    MsgList_init(expected_msg_list);
 | 
					    MsgList_init(expected_msg_list);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    test_rpc_create_simple_message(
 | 
					    test_rpc_create_storage_list_request(&request, path, md5, command_id, filter_max_size);
 | 
				
			||||||
        &request, PB_Main_storage_list_request_tag, path, command_id, md5);
 | 
					 | 
				
			||||||
    if(!strcmp(path, "/")) {
 | 
					    if(!strcmp(path, "/")) {
 | 
				
			||||||
        test_rpc_storage_list_create_expected_list_root(expected_msg_list, command_id);
 | 
					        test_rpc_storage_list_create_expected_list_root(expected_msg_list, command_id);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        test_rpc_storage_list_create_expected_list(expected_msg_list, path, command_id, md5);
 | 
					        test_rpc_storage_list_create_expected_list(
 | 
				
			||||||
 | 
					            expected_msg_list, path, command_id, md5, filter_max_size);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    test_rpc_encode_and_feed_one(&request, 0);
 | 
					    test_rpc_encode_and_feed_one(&request, 0);
 | 
				
			||||||
    test_rpc_decode_and_compare(expected_msg_list, 0);
 | 
					    test_rpc_decode_and_compare(expected_msg_list, 0);
 | 
				
			||||||
@ -718,25 +751,32 @@ static void test_rpc_storage_list_run(const char* path, uint32_t command_id, boo
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
MU_TEST(test_storage_list) {
 | 
					MU_TEST(test_storage_list) {
 | 
				
			||||||
    test_rpc_storage_list_run("/", ++command_id, false);
 | 
					    test_rpc_storage_list_run("/", ++command_id, false, 0);
 | 
				
			||||||
    test_rpc_storage_list_run(EXT_PATH("nfc"), ++command_id, false);
 | 
					    test_rpc_storage_list_run(EXT_PATH("nfc"), ++command_id, false, 0);
 | 
				
			||||||
    test_rpc_storage_list_run(STORAGE_INT_PATH_PREFIX, ++command_id, false);
 | 
					    test_rpc_storage_list_run(STORAGE_INT_PATH_PREFIX, ++command_id, false, 0);
 | 
				
			||||||
    test_rpc_storage_list_run(STORAGE_EXT_PATH_PREFIX, ++command_id, false);
 | 
					    test_rpc_storage_list_run(STORAGE_EXT_PATH_PREFIX, ++command_id, false, 0);
 | 
				
			||||||
    test_rpc_storage_list_run(EXT_PATH("infrared"), ++command_id, false);
 | 
					    test_rpc_storage_list_run(EXT_PATH("infrared"), ++command_id, false, 0);
 | 
				
			||||||
    test_rpc_storage_list_run(EXT_PATH("ibutton"), ++command_id, false);
 | 
					    test_rpc_storage_list_run(EXT_PATH("ibutton"), ++command_id, false, 0);
 | 
				
			||||||
    test_rpc_storage_list_run(EXT_PATH("lfrfid"), ++command_id, false);
 | 
					    test_rpc_storage_list_run(EXT_PATH("lfrfid"), ++command_id, false, 0);
 | 
				
			||||||
    test_rpc_storage_list_run("error_path", ++command_id, false);
 | 
					    test_rpc_storage_list_run("error_path", ++command_id, false, 0);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
MU_TEST(test_storage_list_md5) {
 | 
					MU_TEST(test_storage_list_md5) {
 | 
				
			||||||
    test_rpc_storage_list_run("/", ++command_id, true);
 | 
					    test_rpc_storage_list_run("/", ++command_id, true, 0);
 | 
				
			||||||
    test_rpc_storage_list_run(EXT_PATH("nfc"), ++command_id, true);
 | 
					    test_rpc_storage_list_run(EXT_PATH("nfc"), ++command_id, true, 0);
 | 
				
			||||||
    test_rpc_storage_list_run(STORAGE_INT_PATH_PREFIX, ++command_id, true);
 | 
					    test_rpc_storage_list_run(STORAGE_INT_PATH_PREFIX, ++command_id, true, 0);
 | 
				
			||||||
    test_rpc_storage_list_run(STORAGE_EXT_PATH_PREFIX, ++command_id, true);
 | 
					    test_rpc_storage_list_run(STORAGE_EXT_PATH_PREFIX, ++command_id, true, 0);
 | 
				
			||||||
    test_rpc_storage_list_run(EXT_PATH("infrared"), ++command_id, true);
 | 
					    test_rpc_storage_list_run(EXT_PATH("infrared"), ++command_id, true, 0);
 | 
				
			||||||
    test_rpc_storage_list_run(EXT_PATH("ibutton"), ++command_id, true);
 | 
					    test_rpc_storage_list_run(EXT_PATH("ibutton"), ++command_id, true, 0);
 | 
				
			||||||
    test_rpc_storage_list_run(EXT_PATH("lfrfid"), ++command_id, true);
 | 
					    test_rpc_storage_list_run(EXT_PATH("lfrfid"), ++command_id, true, 0);
 | 
				
			||||||
    test_rpc_storage_list_run("error_path", ++command_id, true);
 | 
					    test_rpc_storage_list_run("error_path", ++command_id, true, 0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MU_TEST(test_storage_list_size) {
 | 
				
			||||||
 | 
					    test_rpc_storage_list_run(TEST_DIR_NAME, ++command_id, false, 0);
 | 
				
			||||||
 | 
					    test_rpc_storage_list_run(TEST_DIR_NAME, ++command_id, false, 1);
 | 
				
			||||||
 | 
					    test_rpc_storage_list_run(TEST_DIR_NAME, ++command_id, false, 1000);
 | 
				
			||||||
 | 
					    test_rpc_storage_list_run(TEST_DIR_NAME, ++command_id, false, 2500);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void
 | 
					static void
 | 
				
			||||||
@ -804,8 +844,7 @@ static void test_storage_read_run(const char* path, uint32_t command_id) {
 | 
				
			|||||||
    MsgList_init(expected_msg_list);
 | 
					    MsgList_init(expected_msg_list);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    test_rpc_add_read_to_list_by_reading_real_file(expected_msg_list, path, command_id);
 | 
					    test_rpc_add_read_to_list_by_reading_real_file(expected_msg_list, path, command_id);
 | 
				
			||||||
    test_rpc_create_simple_message(
 | 
					    test_rpc_create_simple_message(&request, PB_Main_storage_read_request_tag, path, command_id);
 | 
				
			||||||
        &request, PB_Main_storage_read_request_tag, path, command_id, false);
 | 
					 | 
				
			||||||
    test_rpc_encode_and_feed_one(&request, 0);
 | 
					    test_rpc_encode_and_feed_one(&request, 0);
 | 
				
			||||||
    test_rpc_decode_and_compare(expected_msg_list, 0);
 | 
					    test_rpc_decode_and_compare(expected_msg_list, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -859,8 +898,7 @@ static void test_rpc_storage_info_run(const char* path, uint32_t command_id) {
 | 
				
			|||||||
    MsgList_t expected_msg_list;
 | 
					    MsgList_t expected_msg_list;
 | 
				
			||||||
    MsgList_init(expected_msg_list);
 | 
					    MsgList_init(expected_msg_list);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    test_rpc_create_simple_message(
 | 
					    test_rpc_create_simple_message(&request, PB_Main_storage_info_request_tag, path, command_id);
 | 
				
			||||||
        &request, PB_Main_storage_info_request_tag, path, command_id, false);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    PB_Main* response = MsgList_push_new(expected_msg_list);
 | 
					    PB_Main* response = MsgList_push_new(expected_msg_list);
 | 
				
			||||||
    response->command_id = command_id;
 | 
					    response->command_id = command_id;
 | 
				
			||||||
@ -892,8 +930,7 @@ static void test_rpc_storage_stat_run(const char* path, uint32_t command_id) {
 | 
				
			|||||||
    MsgList_t expected_msg_list;
 | 
					    MsgList_t expected_msg_list;
 | 
				
			||||||
    MsgList_init(expected_msg_list);
 | 
					    MsgList_init(expected_msg_list);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    test_rpc_create_simple_message(
 | 
					    test_rpc_create_simple_message(&request, PB_Main_storage_stat_request_tag, path, command_id);
 | 
				
			||||||
        &request, PB_Main_storage_stat_request_tag, path, command_id, false);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Storage* fs_api = furi_record_open(RECORD_STORAGE);
 | 
					    Storage* fs_api = furi_record_open(RECORD_STORAGE);
 | 
				
			||||||
    FileInfo fileinfo;
 | 
					    FileInfo fileinfo;
 | 
				
			||||||
@ -1005,11 +1042,7 @@ static void test_storage_write_read_run(
 | 
				
			|||||||
    test_rpc_add_empty_to_list(expected_msg_list, PB_CommandStatus_OK, *command_id);
 | 
					    test_rpc_add_empty_to_list(expected_msg_list, PB_CommandStatus_OK, *command_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    test_rpc_create_simple_message(
 | 
					    test_rpc_create_simple_message(
 | 
				
			||||||
        MsgList_push_raw(input_msg_list),
 | 
					        MsgList_push_raw(input_msg_list), PB_Main_storage_read_request_tag, path, ++*command_id);
 | 
				
			||||||
        PB_Main_storage_read_request_tag,
 | 
					 | 
				
			||||||
        path,
 | 
					 | 
				
			||||||
        ++*command_id,
 | 
					 | 
				
			||||||
        false);
 | 
					 | 
				
			||||||
    test_rpc_add_read_or_write_to_list(
 | 
					    test_rpc_add_read_or_write_to_list(
 | 
				
			||||||
        expected_msg_list,
 | 
					        expected_msg_list,
 | 
				
			||||||
        READ_RESPONSE,
 | 
					        READ_RESPONSE,
 | 
				
			||||||
@ -1082,8 +1115,7 @@ MU_TEST(test_storage_interrupt_continuous_same_system) {
 | 
				
			|||||||
        MsgList_push_new(input_msg_list),
 | 
					        MsgList_push_new(input_msg_list),
 | 
				
			||||||
        PB_Main_storage_mkdir_request_tag,
 | 
					        PB_Main_storage_mkdir_request_tag,
 | 
				
			||||||
        TEST_DIR "dir1",
 | 
					        TEST_DIR "dir1",
 | 
				
			||||||
        command_id + 1,
 | 
					        command_id + 1);
 | 
				
			||||||
        false);
 | 
					 | 
				
			||||||
    test_rpc_add_read_or_write_to_list(
 | 
					    test_rpc_add_read_or_write_to_list(
 | 
				
			||||||
        input_msg_list,
 | 
					        input_msg_list,
 | 
				
			||||||
        WRITE_REQUEST,
 | 
					        WRITE_REQUEST,
 | 
				
			||||||
@ -1163,8 +1195,7 @@ static void test_storage_delete_run(
 | 
				
			|||||||
    MsgList_t expected_msg_list;
 | 
					    MsgList_t expected_msg_list;
 | 
				
			||||||
    MsgList_init(expected_msg_list);
 | 
					    MsgList_init(expected_msg_list);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    test_rpc_create_simple_message(
 | 
					    test_rpc_create_simple_message(&request, PB_Main_storage_delete_request_tag, path, command_id);
 | 
				
			||||||
        &request, PB_Main_storage_delete_request_tag, path, command_id, false);
 | 
					 | 
				
			||||||
    request.content.storage_delete_request.recursive = recursive;
 | 
					    request.content.storage_delete_request.recursive = recursive;
 | 
				
			||||||
    test_rpc_add_empty_to_list(expected_msg_list, status, command_id);
 | 
					    test_rpc_add_empty_to_list(expected_msg_list, status, command_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1245,8 +1276,7 @@ static void test_storage_mkdir_run(const char* path, size_t command_id, PB_Comma
 | 
				
			|||||||
    MsgList_t expected_msg_list;
 | 
					    MsgList_t expected_msg_list;
 | 
				
			||||||
    MsgList_init(expected_msg_list);
 | 
					    MsgList_init(expected_msg_list);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    test_rpc_create_simple_message(
 | 
					    test_rpc_create_simple_message(&request, PB_Main_storage_mkdir_request_tag, path, command_id);
 | 
				
			||||||
        &request, PB_Main_storage_mkdir_request_tag, path, command_id, false);
 | 
					 | 
				
			||||||
    test_rpc_add_empty_to_list(expected_msg_list, status, command_id);
 | 
					    test_rpc_add_empty_to_list(expected_msg_list, status, command_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    test_rpc_encode_and_feed_one(&request, 0);
 | 
					    test_rpc_encode_and_feed_one(&request, 0);
 | 
				
			||||||
@ -1297,12 +1327,11 @@ static void test_storage_md5sum_run(
 | 
				
			|||||||
    MsgList_t expected_msg_list;
 | 
					    MsgList_t expected_msg_list;
 | 
				
			||||||
    MsgList_init(expected_msg_list);
 | 
					    MsgList_init(expected_msg_list);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    test_rpc_create_simple_message(
 | 
					    test_rpc_create_simple_message(&request, PB_Main_storage_md5sum_request_tag, path, command_id);
 | 
				
			||||||
        &request, PB_Main_storage_md5sum_request_tag, path, command_id, false);
 | 
					 | 
				
			||||||
    if(status == PB_CommandStatus_OK) {
 | 
					    if(status == PB_CommandStatus_OK) {
 | 
				
			||||||
        PB_Main* response = MsgList_push_new(expected_msg_list);
 | 
					        PB_Main* response = MsgList_push_new(expected_msg_list);
 | 
				
			||||||
        test_rpc_create_simple_message(
 | 
					        test_rpc_create_simple_message(
 | 
				
			||||||
            response, PB_Main_storage_md5sum_response_tag, md5sum, command_id, false);
 | 
					            response, PB_Main_storage_md5sum_response_tag, md5sum, command_id);
 | 
				
			||||||
        response->command_status = status;
 | 
					        response->command_status = status;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        test_rpc_add_empty_to_list(expected_msg_list, status, command_id);
 | 
					        test_rpc_add_empty_to_list(expected_msg_list, status, command_id);
 | 
				
			||||||
@ -1461,6 +1490,7 @@ MU_TEST_SUITE(test_rpc_storage) {
 | 
				
			|||||||
    MU_RUN_TEST(test_storage_stat);
 | 
					    MU_RUN_TEST(test_storage_stat);
 | 
				
			||||||
    MU_RUN_TEST(test_storage_list);
 | 
					    MU_RUN_TEST(test_storage_list);
 | 
				
			||||||
    MU_RUN_TEST(test_storage_list_md5);
 | 
					    MU_RUN_TEST(test_storage_list_md5);
 | 
				
			||||||
 | 
					    MU_RUN_TEST(test_storage_list_size);
 | 
				
			||||||
    MU_RUN_TEST(test_storage_read);
 | 
					    MU_RUN_TEST(test_storage_read);
 | 
				
			||||||
    MU_RUN_TEST(test_storage_write_read);
 | 
					    MU_RUN_TEST(test_storage_write_read);
 | 
				
			||||||
    MU_RUN_TEST(test_storage_write);
 | 
					    MU_RUN_TEST(test_storage_write);
 | 
				
			||||||
@ -1759,8 +1789,7 @@ MU_TEST(test_rpc_multisession_storage) {
 | 
				
			|||||||
        MsgList_push_raw(input_0),
 | 
					        MsgList_push_raw(input_0),
 | 
				
			||||||
        PB_Main_storage_read_request_tag,
 | 
					        PB_Main_storage_read_request_tag,
 | 
				
			||||||
        TEST_DIR "file0.txt",
 | 
					        TEST_DIR "file0.txt",
 | 
				
			||||||
        ++command_id,
 | 
					        ++command_id);
 | 
				
			||||||
        false);
 | 
					 | 
				
			||||||
    test_rpc_add_read_or_write_to_list(
 | 
					    test_rpc_add_read_or_write_to_list(
 | 
				
			||||||
        expected_0, READ_RESPONSE, TEST_DIR "file0.txt", pattern, sizeof(pattern), 1, command_id);
 | 
					        expected_0, READ_RESPONSE, TEST_DIR "file0.txt", pattern, sizeof(pattern), 1, command_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1768,8 +1797,7 @@ MU_TEST(test_rpc_multisession_storage) {
 | 
				
			|||||||
        MsgList_push_raw(input_1),
 | 
					        MsgList_push_raw(input_1),
 | 
				
			||||||
        PB_Main_storage_read_request_tag,
 | 
					        PB_Main_storage_read_request_tag,
 | 
				
			||||||
        TEST_DIR "file1.txt",
 | 
					        TEST_DIR "file1.txt",
 | 
				
			||||||
        ++command_id,
 | 
					        ++command_id);
 | 
				
			||||||
        false);
 | 
					 | 
				
			||||||
    test_rpc_add_read_or_write_to_list(
 | 
					    test_rpc_add_read_or_write_to_list(
 | 
				
			||||||
        expected_1, READ_RESPONSE, TEST_DIR "file1.txt", pattern, sizeof(pattern), 1, command_id);
 | 
					        expected_1, READ_RESPONSE, TEST_DIR "file1.txt", pattern, sizeof(pattern), 1, command_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,7 @@
 | 
				
			|||||||
#include <lib/subghz/devices/devices.h>
 | 
					#include <lib/subghz/devices/devices.h>
 | 
				
			||||||
#include <lib/subghz/devices/cc1101_configs.h>
 | 
					#include <lib/subghz/devices/cc1101_configs.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define TAG "SubGhz TEST"
 | 
					#define TAG "SubGhzTest"
 | 
				
			||||||
#define KEYSTORE_DIR_NAME EXT_PATH("subghz/assets/keeloq_mfcodes")
 | 
					#define KEYSTORE_DIR_NAME EXT_PATH("subghz/assets/keeloq_mfcodes")
 | 
				
			||||||
#define CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo")
 | 
					#define CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo")
 | 
				
			||||||
#define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s")
 | 
					#define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s")
 | 
				
			||||||
 | 
				
			|||||||
@ -16,7 +16,7 @@
 | 
				
			|||||||
#include <cc1101.h>
 | 
					#include <cc1101.h>
 | 
				
			||||||
#include <stdio.h>
 | 
					#include <stdio.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define TAG "SubGhz_Device_CC1101_Ext"
 | 
					#define TAG "SubGhzDeviceCc1101Ext"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define SUBGHZ_DEVICE_CC1101_EXT_TX_GPIO &gpio_ext_pb2
 | 
					#define SUBGHZ_DEVICE_CC1101_EXT_TX_GPIO &gpio_ext_pb2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -333,7 +333,7 @@ bool subghz_device_cc1101_ext_rx_pipe_not_empty() {
 | 
				
			|||||||
        (CC1101_STATUS_RXBYTES) | CC1101_BURST,
 | 
					        (CC1101_STATUS_RXBYTES) | CC1101_BURST,
 | 
				
			||||||
        (uint8_t*)status);
 | 
					        (uint8_t*)status);
 | 
				
			||||||
    furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle);
 | 
					    furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle);
 | 
				
			||||||
    if((status->NUM_RXBYTES > 0) || (status->RXFIFO_OVERFLOW == 0)) {
 | 
					    if(status->NUM_RXBYTES > 0) {
 | 
				
			||||||
        return true;
 | 
					        return true;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@
 | 
				
			|||||||
#include "cc1101_ext.h"
 | 
					#include "cc1101_ext.h"
 | 
				
			||||||
#include <lib/subghz/devices/cc1101_configs.h>
 | 
					#include <lib/subghz/devices/cc1101_configs.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define TAG "SubGhzDeviceCC1101Ext"
 | 
					#define TAG "SubGhzDeviceCc1101Ext"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static bool subghz_device_cc1101_ext_interconnect_is_frequency_valid(uint32_t frequency) {
 | 
					static bool subghz_device_cc1101_ext_interconnect_is_frequency_valid(uint32_t frequency) {
 | 
				
			||||||
    bool ret = subghz_device_cc1101_ext_is_frequency_valid(frequency);
 | 
					    bool ret = subghz_device_cc1101_ext_is_frequency_valid(frequency);
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,7 @@
 | 
				
			|||||||
#include <toolbox/stream/file_stream.h>
 | 
					#include <toolbox/stream/file_stream.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Define log tag
 | 
					// Define log tag
 | 
				
			||||||
#define TAG "example_apps_assets"
 | 
					#define TAG "ExampleAppsAssets"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void example_apps_data_print_file_content(Storage* storage, const char* path) {
 | 
					static void example_apps_data_print_file_content(Storage* storage, const char* path) {
 | 
				
			||||||
    Stream* stream = file_stream_alloc(storage);
 | 
					    Stream* stream = file_stream_alloc(storage);
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@
 | 
				
			|||||||
#include <storage/storage.h>
 | 
					#include <storage/storage.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Define log tag
 | 
					// Define log tag
 | 
				
			||||||
#define TAG "example_apps_data"
 | 
					#define TAG "ExampleAppsData"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Application entry point
 | 
					// Application entry point
 | 
				
			||||||
int32_t example_apps_data_main(void* p) {
 | 
					int32_t example_apps_data_main(void* p) {
 | 
				
			||||||
 | 
				
			|||||||
@ -11,7 +11,7 @@
 | 
				
			|||||||
#include <loader/firmware_api/firmware_api.h>
 | 
					#include <loader/firmware_api/firmware_api.h>
 | 
				
			||||||
#include <storage/storage.h>
 | 
					#include <storage/storage.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define TAG "example_plugins"
 | 
					#define TAG "ExamplePlugins"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int32_t example_plugins_app(void* p) {
 | 
					int32_t example_plugins_app(void* p) {
 | 
				
			||||||
    UNUSED(p);
 | 
					    UNUSED(p);
 | 
				
			||||||
 | 
				
			|||||||
@ -11,7 +11,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include <furi.h>
 | 
					#include <furi.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define TAG "example_plugins"
 | 
					#define TAG "ExamplePlugins"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int32_t example_plugins_multi_app(void* p) {
 | 
					int32_t example_plugins_multi_app(void* p) {
 | 
				
			||||||
    UNUSED(p);
 | 
					    UNUSED(p);
 | 
				
			||||||
 | 
				
			|||||||
@ -8,7 +8,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include <loader/firmware_api/firmware_api.h>
 | 
					#include <loader/firmware_api/firmware_api.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define TAG "example_advanced_plugins"
 | 
					#define TAG "ExampleAdvancedPlugins"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int32_t example_advanced_plugins_app(void* p) {
 | 
					int32_t example_advanced_plugins_app(void* p) {
 | 
				
			||||||
    UNUSED(p);
 | 
					    UNUSED(p);
 | 
				
			||||||
 | 
				
			|||||||
@ -9,7 +9,7 @@
 | 
				
			|||||||
#include "ducky_script_i.h"
 | 
					#include "ducky_script_i.h"
 | 
				
			||||||
#include <dolphin/dolphin.h>
 | 
					#include <dolphin/dolphin.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define TAG "BadUSB"
 | 
					#define TAG "BadUsb"
 | 
				
			||||||
#define WORKER_TAG TAG "Worker"
 | 
					#define WORKER_TAG TAG "Worker"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define BADUSB_ASCII_TO_KEY(script, x) \
 | 
					#define BADUSB_ASCII_TO_KEY(script, x) \
 | 
				
			||||||
 | 
				
			|||||||
@ -171,7 +171,7 @@ static const DuckyCmd ducky_commands[] = {
 | 
				
			|||||||
    {"WAIT_FOR_BUTTON_PRESS", ducky_fnc_waitforbutton, -1},
 | 
					    {"WAIT_FOR_BUTTON_PRESS", ducky_fnc_waitforbutton, -1},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define TAG "BadUSB"
 | 
					#define TAG "BadUsb"
 | 
				
			||||||
#define WORKER_TAG TAG "Worker"
 | 
					#define WORKER_TAG TAG "Worker"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int32_t ducky_execute_cmd(BadUsbScript* bad_usb, const char* line) {
 | 
					int32_t ducky_execute_cmd(BadUsbScript* bad_usb, const char* line) {
 | 
				
			||||||
 | 
				
			|||||||
@ -18,10 +18,12 @@ GPIOItems* gpio_items_alloc() {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    items->pins = malloc(sizeof(GpioPinRecord) * items->count);
 | 
					    items->pins = malloc(sizeof(GpioPinRecord) * items->count);
 | 
				
			||||||
    for(size_t i = 0; i < items->count; i++) {
 | 
					    size_t index = 0;
 | 
				
			||||||
 | 
					    for(size_t i = 0; i < gpio_pins_count; i++) {
 | 
				
			||||||
        if(!gpio_pins[i].debug) {
 | 
					        if(!gpio_pins[i].debug) {
 | 
				
			||||||
            items->pins[i].pin = gpio_pins[i].pin;
 | 
					            items->pins[index].pin = gpio_pins[i].pin;
 | 
				
			||||||
            items->pins[i].name = gpio_pins[i].name;
 | 
					            items->pins[index].name = gpio_pins[i].name;
 | 
				
			||||||
 | 
					            index++;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return items;
 | 
					    return items;
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,7 @@
 | 
				
			|||||||
#include <toolbox/path.h>
 | 
					#include <toolbox/path.h>
 | 
				
			||||||
#include <dolphin/dolphin.h>
 | 
					#include <dolphin/dolphin.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define TAG "iButtonApp"
 | 
					#define TAG "IButtonApp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const NotificationSequence sequence_blink_set_yellow = {
 | 
					static const NotificationSequence sequence_blink_set_yellow = {
 | 
				
			||||||
    &message_blink_set_color_yellow,
 | 
					    &message_blink_set_color_yellow,
 | 
				
			||||||
@ -195,16 +195,23 @@ bool ibutton_load_key(iButton* ibutton) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
bool ibutton_select_and_load_key(iButton* ibutton) {
 | 
					bool ibutton_select_and_load_key(iButton* ibutton) {
 | 
				
			||||||
    DialogsFileBrowserOptions browser_options;
 | 
					    DialogsFileBrowserOptions browser_options;
 | 
				
			||||||
    dialog_file_browser_set_basic_options(&browser_options, IBUTTON_APP_EXTENSION, &I_ibutt_10px);
 | 
					    bool success = false;
 | 
				
			||||||
 | 
					    dialog_file_browser_set_basic_options(
 | 
				
			||||||
 | 
					        &browser_options, IBUTTON_APP_FILENAME_EXTENSION, &I_ibutt_10px);
 | 
				
			||||||
    browser_options.base_path = IBUTTON_APP_FOLDER;
 | 
					    browser_options.base_path = IBUTTON_APP_FOLDER;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(furi_string_empty(ibutton->file_path)) {
 | 
					    if(furi_string_empty(ibutton->file_path)) {
 | 
				
			||||||
        furi_string_set(ibutton->file_path, browser_options.base_path);
 | 
					        furi_string_set(ibutton->file_path, browser_options.base_path);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return dialog_file_browser_show(
 | 
					    do {
 | 
				
			||||||
               ibutton->dialogs, ibutton->file_path, ibutton->file_path, &browser_options) &&
 | 
					        if(!dialog_file_browser_show(
 | 
				
			||||||
           ibutton_load_key(ibutton);
 | 
					               ibutton->dialogs, ibutton->file_path, ibutton->file_path, &browser_options))
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        success = ibutton_load_key(ibutton);
 | 
				
			||||||
 | 
					    } while(!success);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return success;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool ibutton_save_key(iButton* ibutton) {
 | 
					bool ibutton_save_key(iButton* ibutton) {
 | 
				
			||||||
 | 
				
			|||||||
@ -29,7 +29,8 @@
 | 
				
			|||||||
#include "scenes/ibutton_scene.h"
 | 
					#include "scenes/ibutton_scene.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define IBUTTON_APP_FOLDER ANY_PATH("ibutton")
 | 
					#define IBUTTON_APP_FOLDER ANY_PATH("ibutton")
 | 
				
			||||||
#define IBUTTON_APP_EXTENSION ".ibtn"
 | 
					#define IBUTTON_APP_FILENAME_PREFIX "iBtn"
 | 
				
			||||||
 | 
					#define IBUTTON_APP_FILENAME_EXTENSION ".ibtn"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define IBUTTON_KEY_NAME_SIZE 22
 | 
					#define IBUTTON_KEY_NAME_SIZE 22
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
#include "../ibutton_i.h"
 | 
					#include "../ibutton_i.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <toolbox/random_name.h>
 | 
					#include <toolbox/name_generator.h>
 | 
				
			||||||
#include <toolbox/path.h>
 | 
					#include <toolbox/path.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <dolphin/dolphin.h>
 | 
					#include <dolphin/dolphin.h>
 | 
				
			||||||
@ -17,7 +17,8 @@ void ibutton_scene_save_name_on_enter(void* context) {
 | 
				
			|||||||
    const bool is_new_file = furi_string_empty(ibutton->file_path);
 | 
					    const bool is_new_file = furi_string_empty(ibutton->file_path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(is_new_file) {
 | 
					    if(is_new_file) {
 | 
				
			||||||
        set_random_name(ibutton->key_name, IBUTTON_KEY_NAME_SIZE);
 | 
					        name_generator_make_auto(
 | 
				
			||||||
 | 
					            ibutton->key_name, IBUTTON_KEY_NAME_SIZE, IBUTTON_APP_FILENAME_PREFIX);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    text_input_set_header_text(text_input, "Name the key");
 | 
					    text_input_set_header_text(text_input, "Name the key");
 | 
				
			||||||
@ -29,8 +30,8 @@ void ibutton_scene_save_name_on_enter(void* context) {
 | 
				
			|||||||
        IBUTTON_KEY_NAME_SIZE,
 | 
					        IBUTTON_KEY_NAME_SIZE,
 | 
				
			||||||
        is_new_file);
 | 
					        is_new_file);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ValidatorIsFile* validator_is_file =
 | 
					    ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
 | 
				
			||||||
        validator_is_file_alloc_init(IBUTTON_APP_FOLDER, IBUTTON_APP_EXTENSION, ibutton->key_name);
 | 
					        IBUTTON_APP_FOLDER, IBUTTON_APP_FILENAME_EXTENSION, ibutton->key_name);
 | 
				
			||||||
    text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
 | 
					    text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewTextInput);
 | 
					    view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewTextInput);
 | 
				
			||||||
@ -48,7 +49,7 @@ bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) {
 | 
				
			|||||||
                "%s/%s%s",
 | 
					                "%s/%s%s",
 | 
				
			||||||
                IBUTTON_APP_FOLDER,
 | 
					                IBUTTON_APP_FOLDER,
 | 
				
			||||||
                ibutton->key_name,
 | 
					                ibutton->key_name,
 | 
				
			||||||
                IBUTTON_APP_EXTENSION);
 | 
					                IBUTTON_APP_FILENAME_EXTENSION);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if(ibutton_save_key(ibutton)) {
 | 
					            if(ibutton_save_key(ibutton)) {
 | 
				
			||||||
                scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSaveSuccess);
 | 
					                scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSaveSuccess);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,6 @@
 | 
				
			|||||||
#include "../infrared_i.h"
 | 
					#include "../infrared_i.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "common/infrared_scene_universal_common.h"
 | 
					#include "common/infrared_scene_universal_common.h"
 | 
				
			||||||
#include <furi_hal_rtc.h>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
void infrared_scene_universal_ac_on_enter(void* context) {
 | 
					void infrared_scene_universal_ac_on_enter(void* context) {
 | 
				
			||||||
    infrared_scene_universal_common_on_enter(context);
 | 
					    infrared_scene_universal_common_on_enter(context);
 | 
				
			||||||
 | 
				
			|||||||
@ -215,13 +215,16 @@ bool lfrfid_save_key(LfRfid* app) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    lfrfid_make_app_folder(app);
 | 
					    lfrfid_make_app_folder(app);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(furi_string_end_with(app->file_path, LFRFID_APP_EXTENSION)) {
 | 
					    if(furi_string_end_with(app->file_path, LFRFID_APP_FILENAME_EXTENSION)) {
 | 
				
			||||||
        size_t filename_start = furi_string_search_rchar(app->file_path, '/');
 | 
					        size_t filename_start = furi_string_search_rchar(app->file_path, '/');
 | 
				
			||||||
        furi_string_left(app->file_path, filename_start);
 | 
					        furi_string_left(app->file_path, filename_start);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    furi_string_cat_printf(
 | 
					    furi_string_cat_printf(
 | 
				
			||||||
        app->file_path, "/%s%s", furi_string_get_cstr(app->file_name), LFRFID_APP_EXTENSION);
 | 
					        app->file_path,
 | 
				
			||||||
 | 
					        "/%s%s",
 | 
				
			||||||
 | 
					        furi_string_get_cstr(app->file_name),
 | 
				
			||||||
 | 
					        LFRFID_APP_FILENAME_EXTENSION);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    result = lfrfid_save_key_data(app, app->file_path);
 | 
					    result = lfrfid_save_key_data(app, app->file_path);
 | 
				
			||||||
    return result;
 | 
					    return result;
 | 
				
			||||||
@ -231,7 +234,8 @@ bool lfrfid_load_key_from_file_select(LfRfid* app) {
 | 
				
			|||||||
    furi_assert(app);
 | 
					    furi_assert(app);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    DialogsFileBrowserOptions browser_options;
 | 
					    DialogsFileBrowserOptions browser_options;
 | 
				
			||||||
    dialog_file_browser_set_basic_options(&browser_options, LFRFID_APP_EXTENSION, &I_125_10px);
 | 
					    dialog_file_browser_set_basic_options(
 | 
				
			||||||
 | 
					        &browser_options, LFRFID_APP_FILENAME_EXTENSION, &I_125_10px);
 | 
				
			||||||
    browser_options.base_path = LFRFID_APP_FOLDER;
 | 
					    browser_options.base_path = LFRFID_APP_FOLDER;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Input events and views are managed by file_browser
 | 
					    // Input events and views are managed by file_browser
 | 
				
			||||||
 | 
				
			|||||||
@ -40,8 +40,9 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#define LFRFID_APP_FOLDER ANY_PATH("lfrfid")
 | 
					#define LFRFID_APP_FOLDER ANY_PATH("lfrfid")
 | 
				
			||||||
#define LFRFID_SD_FOLDER EXT_PATH("lfrfid")
 | 
					#define LFRFID_SD_FOLDER EXT_PATH("lfrfid")
 | 
				
			||||||
#define LFRFID_APP_EXTENSION ".rfid"
 | 
					#define LFRFID_APP_FILENAME_PREFIX "RFID"
 | 
				
			||||||
#define LFRFID_APP_SHADOW_EXTENSION ".shd"
 | 
					#define LFRFID_APP_FILENAME_EXTENSION ".rfid"
 | 
				
			||||||
 | 
					#define LFRFID_APP_SHADOW_FILENAME_EXTENSION ".shd"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define LFRFID_APP_RAW_ASK_EXTENSION ".ask.raw"
 | 
					#define LFRFID_APP_RAW_ASK_EXTENSION ".ask.raw"
 | 
				
			||||||
#define LFRFID_APP_RAW_PSK_EXTENSION ".psk.raw"
 | 
					#define LFRFID_APP_RAW_PSK_EXTENSION ".psk.raw"
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
#include <lib/toolbox/random_name.h>
 | 
					 | 
				
			||||||
#include "../lfrfid_i.h"
 | 
					#include "../lfrfid_i.h"
 | 
				
			||||||
#include <dolphin/dolphin.h>
 | 
					#include <dolphin/dolphin.h>
 | 
				
			||||||
 | 
					#include <toolbox/name_generator.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void lfrfid_scene_save_name_on_enter(void* context) {
 | 
					void lfrfid_scene_save_name_on_enter(void* context) {
 | 
				
			||||||
    LfRfid* app = context;
 | 
					    LfRfid* app = context;
 | 
				
			||||||
@ -11,7 +11,10 @@ void lfrfid_scene_save_name_on_enter(void* context) {
 | 
				
			|||||||
    bool key_name_is_empty = furi_string_empty(app->file_name);
 | 
					    bool key_name_is_empty = furi_string_empty(app->file_name);
 | 
				
			||||||
    if(key_name_is_empty) {
 | 
					    if(key_name_is_empty) {
 | 
				
			||||||
        furi_string_set(app->file_path, LFRFID_APP_FOLDER);
 | 
					        furi_string_set(app->file_path, LFRFID_APP_FOLDER);
 | 
				
			||||||
        set_random_name(app->text_store, LFRFID_TEXT_STORE_SIZE);
 | 
					
 | 
				
			||||||
 | 
					        name_generator_make_auto(
 | 
				
			||||||
 | 
					            app->text_store, LFRFID_TEXT_STORE_SIZE, LFRFID_APP_FILENAME_PREFIX);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        furi_string_set(folder_path, LFRFID_APP_FOLDER);
 | 
					        furi_string_set(folder_path, LFRFID_APP_FOLDER);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        lfrfid_text_store_set(app, "%s", furi_string_get_cstr(app->file_name));
 | 
					        lfrfid_text_store_set(app, "%s", furi_string_get_cstr(app->file_name));
 | 
				
			||||||
@ -31,7 +34,7 @@ void lfrfid_scene_save_name_on_enter(void* context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
 | 
					    ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
 | 
				
			||||||
        furi_string_get_cstr(folder_path),
 | 
					        furi_string_get_cstr(folder_path),
 | 
				
			||||||
        LFRFID_APP_EXTENSION,
 | 
					        LFRFID_APP_FILENAME_EXTENSION,
 | 
				
			||||||
        furi_string_get_cstr(app->file_name));
 | 
					        furi_string_get_cstr(app->file_name));
 | 
				
			||||||
    text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
 | 
					    text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -223,7 +223,11 @@ void nfc_blink_stop(Nfc* nfc) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
bool nfc_save_file(Nfc* nfc) {
 | 
					bool nfc_save_file(Nfc* nfc) {
 | 
				
			||||||
    furi_string_printf(
 | 
					    furi_string_printf(
 | 
				
			||||||
        nfc->dev->load_path, "%s/%s%s", NFC_APP_FOLDER, nfc->dev->dev_name, NFC_APP_EXTENSION);
 | 
					        nfc->dev->load_path,
 | 
				
			||||||
 | 
					        "%s/%s%s",
 | 
				
			||||||
 | 
					        NFC_APP_FOLDER,
 | 
				
			||||||
 | 
					        nfc->dev->dev_name,
 | 
				
			||||||
 | 
					        NFC_APP_FILENAME_EXTENSION);
 | 
				
			||||||
    bool file_saved = nfc_device_save(nfc->dev, furi_string_get_cstr(nfc->dev->load_path));
 | 
					    bool file_saved = nfc_device_save(nfc->dev, furi_string_get_cstr(nfc->dev->load_path));
 | 
				
			||||||
    return file_saved;
 | 
					    return file_saved;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
#include "../nfc_i.h"
 | 
					#include "../nfc_i.h"
 | 
				
			||||||
#include <lib/toolbox/random_name.h>
 | 
					#include <lib/toolbox/name_generator.h>
 | 
				
			||||||
#include <gui/modules/validators.h>
 | 
					#include <gui/modules/validators.h>
 | 
				
			||||||
#include <toolbox/path.h>
 | 
					#include <toolbox/path.h>
 | 
				
			||||||
#include <dolphin/dolphin.h>
 | 
					#include <dolphin/dolphin.h>
 | 
				
			||||||
@ -17,7 +17,7 @@ void nfc_scene_save_name_on_enter(void* context) {
 | 
				
			|||||||
    TextInput* text_input = nfc->text_input;
 | 
					    TextInput* text_input = nfc->text_input;
 | 
				
			||||||
    bool dev_name_empty = false;
 | 
					    bool dev_name_empty = false;
 | 
				
			||||||
    if(!strcmp(nfc->dev->dev_name, "")) {
 | 
					    if(!strcmp(nfc->dev->dev_name, "")) {
 | 
				
			||||||
        set_random_name(nfc->text_store, sizeof(nfc->text_store));
 | 
					        name_generator_make_auto(nfc->text_store, NFC_DEV_NAME_MAX_LEN, NFC_APP_FILENAME_PREFIX);
 | 
				
			||||||
        dev_name_empty = true;
 | 
					        dev_name_empty = true;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        nfc_text_store_set(nfc, nfc->dev->dev_name);
 | 
					        nfc_text_store_set(nfc, nfc->dev->dev_name);
 | 
				
			||||||
@ -34,14 +34,14 @@ void nfc_scene_save_name_on_enter(void* context) {
 | 
				
			|||||||
    FuriString* folder_path;
 | 
					    FuriString* folder_path;
 | 
				
			||||||
    folder_path = furi_string_alloc();
 | 
					    folder_path = furi_string_alloc();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(furi_string_end_with(nfc->dev->load_path, NFC_APP_EXTENSION)) {
 | 
					    if(furi_string_end_with(nfc->dev->load_path, NFC_APP_FILENAME_EXTENSION)) {
 | 
				
			||||||
        path_extract_dirname(furi_string_get_cstr(nfc->dev->load_path), folder_path);
 | 
					        path_extract_dirname(furi_string_get_cstr(nfc->dev->load_path), folder_path);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        furi_string_set(folder_path, NFC_APP_FOLDER);
 | 
					        furi_string_set(folder_path, NFC_APP_FOLDER);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
 | 
					    ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
 | 
				
			||||||
        furi_string_get_cstr(folder_path), NFC_APP_EXTENSION, nfc->dev->dev_name);
 | 
					        furi_string_get_cstr(folder_path), NFC_APP_FILENAME_EXTENSION, nfc->dev->dev_name);
 | 
				
			||||||
    text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
 | 
					    text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextInput);
 | 
					    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextInput);
 | 
				
			||||||
 | 
				
			|||||||
@ -185,11 +185,12 @@ static uint32_t subghz_txrx_rx(SubGhzTxRx* instance, uint32_t frequency) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
static void subghz_txrx_idle(SubGhzTxRx* instance) {
 | 
					static void subghz_txrx_idle(SubGhzTxRx* instance) {
 | 
				
			||||||
    furi_assert(instance);
 | 
					    furi_assert(instance);
 | 
				
			||||||
    furi_assert(instance->txrx_state != SubGhzTxRxStateSleep);
 | 
					    if(instance->txrx_state != SubGhzTxRxStateSleep) {
 | 
				
			||||||
        subghz_devices_idle(instance->radio_device);
 | 
					        subghz_devices_idle(instance->radio_device);
 | 
				
			||||||
        subghz_txrx_speaker_off(instance);
 | 
					        subghz_txrx_speaker_off(instance);
 | 
				
			||||||
        instance->txrx_state = SubGhzTxRxStateIDLE;
 | 
					        instance->txrx_state = SubGhzTxRxStateIDLE;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void subghz_txrx_rx_end(SubGhzTxRx* instance) {
 | 
					static void subghz_txrx_rx_end(SubGhzTxRx* instance) {
 | 
				
			||||||
    furi_assert(instance);
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
				
			|||||||
@ -5,7 +5,7 @@
 | 
				
			|||||||
#include <lib/toolbox/path.h>
 | 
					#include <lib/toolbox/path.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define RAW_FILE_NAME "Raw_signal_"
 | 
					#define RAW_FILE_NAME "Raw_signal_"
 | 
				
			||||||
#define TAG "SubGhzSceneReadRAW"
 | 
					#define TAG "SubGhzSceneReadRaw"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool subghz_scene_read_raw_update_filename(SubGhz* subghz) {
 | 
					bool subghz_scene_read_raw_update_filename(SubGhz* subghz) {
 | 
				
			||||||
    bool ret = false;
 | 
					    bool ret = false;
 | 
				
			||||||
@ -239,7 +239,11 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            FuriString* temp_str = furi_string_alloc();
 | 
					            FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
            furi_string_printf(
 | 
					            furi_string_printf(
 | 
				
			||||||
                temp_str, "%s/%s%s", SUBGHZ_RAW_FOLDER, RAW_FILE_NAME, SUBGHZ_APP_EXTENSION);
 | 
					                temp_str,
 | 
				
			||||||
 | 
					                "%s/%s%s",
 | 
				
			||||||
 | 
					                SUBGHZ_RAW_FOLDER,
 | 
				
			||||||
 | 
					                RAW_FILE_NAME,
 | 
				
			||||||
 | 
					                SUBGHZ_APP_FILENAME_EXTENSION);
 | 
				
			||||||
            subghz_protocol_raw_gen_fff_data(
 | 
					            subghz_protocol_raw_gen_fff_data(
 | 
				
			||||||
                subghz_txrx_get_fff_data(subghz->txrx),
 | 
					                subghz_txrx_get_fff_data(subghz->txrx),
 | 
				
			||||||
                furi_string_get_cstr(temp_str),
 | 
					                furi_string_get_cstr(temp_str),
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,10 @@
 | 
				
			|||||||
#include "../subghz_i.h"
 | 
					#include "../subghz_i.h"
 | 
				
			||||||
#include "subghz/types.h"
 | 
					#include "subghz/types.h"
 | 
				
			||||||
#include <lib/toolbox/random_name.h>
 | 
					 | 
				
			||||||
#include "../helpers/subghz_custom_event.h"
 | 
					#include "../helpers/subghz_custom_event.h"
 | 
				
			||||||
#include <lib/subghz/protocols/raw.h>
 | 
					#include <lib/subghz/protocols/raw.h>
 | 
				
			||||||
#include <gui/modules/validators.h>
 | 
					#include <gui/modules/validators.h>
 | 
				
			||||||
#include <dolphin/dolphin.h>
 | 
					#include <dolphin/dolphin.h>
 | 
				
			||||||
 | 
					#include <toolbox/name_generator.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define MAX_TEXT_INPUT_LEN 22
 | 
					#define MAX_TEXT_INPUT_LEN 22
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -40,7 +40,9 @@ void subghz_scene_save_name_on_enter(void* context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if(!subghz_path_is_file(subghz->file_path)) {
 | 
					    if(!subghz_path_is_file(subghz->file_path)) {
 | 
				
			||||||
        char file_name_buf[SUBGHZ_MAX_LEN_NAME] = {0};
 | 
					        char file_name_buf[SUBGHZ_MAX_LEN_NAME] = {0};
 | 
				
			||||||
        set_random_name(file_name_buf, SUBGHZ_MAX_LEN_NAME);
 | 
					
 | 
				
			||||||
 | 
					        name_generator_make_auto(file_name_buf, SUBGHZ_MAX_LEN_NAME, SUBGHZ_APP_FILENAME_PREFIX);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        furi_string_set(file_name, file_name_buf);
 | 
					        furi_string_set(file_name, file_name_buf);
 | 
				
			||||||
        furi_string_set(subghz->file_path, SUBGHZ_APP_FOLDER);
 | 
					        furi_string_set(subghz->file_path, SUBGHZ_APP_FOLDER);
 | 
				
			||||||
        //highlighting the entire filename by default
 | 
					        //highlighting the entire filename by default
 | 
				
			||||||
@ -71,7 +73,7 @@ void subghz_scene_save_name_on_enter(void* context) {
 | 
				
			|||||||
        dev_name_empty);
 | 
					        dev_name_empty);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
 | 
					    ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
 | 
				
			||||||
        furi_string_get_cstr(subghz->file_path), SUBGHZ_APP_EXTENSION, "");
 | 
					        furi_string_get_cstr(subghz->file_path), SUBGHZ_APP_FILENAME_EXTENSION, "");
 | 
				
			||||||
    text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
 | 
					    text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    furi_string_free(file_name);
 | 
					    furi_string_free(file_name);
 | 
				
			||||||
@ -94,7 +96,10 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) {
 | 
				
			|||||||
        if(event.event == SubGhzCustomEventSceneSaveName) {
 | 
					        if(event.event == SubGhzCustomEventSceneSaveName) {
 | 
				
			||||||
            if(strcmp(subghz->file_name_tmp, "") != 0) {
 | 
					            if(strcmp(subghz->file_name_tmp, "") != 0) {
 | 
				
			||||||
                furi_string_cat_printf(
 | 
					                furi_string_cat_printf(
 | 
				
			||||||
                    subghz->file_path, "/%s%s", subghz->file_name_tmp, SUBGHZ_APP_EXTENSION);
 | 
					                    subghz->file_path,
 | 
				
			||||||
 | 
					                    "/%s%s",
 | 
				
			||||||
 | 
					                    subghz->file_name_tmp,
 | 
				
			||||||
 | 
					                    SUBGHZ_APP_FILENAME_EXTENSION);
 | 
				
			||||||
                if(subghz_path_is_file(subghz->file_path_tmp)) {
 | 
					                if(subghz_path_is_file(subghz->file_path_tmp)) {
 | 
				
			||||||
                    if(!subghz_rename_file(subghz)) {
 | 
					                    if(!subghz_rename_file(subghz)) {
 | 
				
			||||||
                        return false;
 | 
					                        return false;
 | 
				
			||||||
 | 
				
			|||||||
@ -28,7 +28,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#define SUBGHZ_REGION_FILENAME "/int/.region_data"
 | 
					#define SUBGHZ_REGION_FILENAME "/int/.region_data"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define TAG "SubGhz CLI"
 | 
					#define TAG "SubGhzCli"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void subghz_cli_radio_device_power_on() {
 | 
					static void subghz_cli_radio_device_power_on() {
 | 
				
			||||||
    uint8_t attempts = 5;
 | 
					    uint8_t attempts = 5;
 | 
				
			||||||
 | 
				
			|||||||
@ -238,7 +238,7 @@ bool subghz_get_next_name_file(SubGhz* subghz, uint8_t max_len) {
 | 
				
			|||||||
            storage,
 | 
					            storage,
 | 
				
			||||||
            furi_string_get_cstr(file_path),
 | 
					            furi_string_get_cstr(file_path),
 | 
				
			||||||
            furi_string_get_cstr(file_name),
 | 
					            furi_string_get_cstr(file_name),
 | 
				
			||||||
            SUBGHZ_APP_EXTENSION,
 | 
					            SUBGHZ_APP_FILENAME_EXTENSION,
 | 
				
			||||||
            file_name,
 | 
					            file_name,
 | 
				
			||||||
            max_len);
 | 
					            max_len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -247,7 +247,7 @@ bool subghz_get_next_name_file(SubGhz* subghz, uint8_t max_len) {
 | 
				
			|||||||
            "%s/%s%s",
 | 
					            "%s/%s%s",
 | 
				
			||||||
            furi_string_get_cstr(file_path),
 | 
					            furi_string_get_cstr(file_path),
 | 
				
			||||||
            furi_string_get_cstr(file_name),
 | 
					            furi_string_get_cstr(file_name),
 | 
				
			||||||
            SUBGHZ_APP_EXTENSION);
 | 
					            SUBGHZ_APP_FILENAME_EXTENSION);
 | 
				
			||||||
        furi_string_set(subghz->file_path, temp_str);
 | 
					        furi_string_set(subghz->file_path, temp_str);
 | 
				
			||||||
        res = true;
 | 
					        res = true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -320,7 +320,8 @@ bool subghz_load_protocol_from_file(SubGhz* subghz) {
 | 
				
			|||||||
    FuriString* file_path = furi_string_alloc();
 | 
					    FuriString* file_path = furi_string_alloc();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    DialogsFileBrowserOptions browser_options;
 | 
					    DialogsFileBrowserOptions browser_options;
 | 
				
			||||||
    dialog_file_browser_set_basic_options(&browser_options, SUBGHZ_APP_EXTENSION, &I_sub1_10px);
 | 
					    dialog_file_browser_set_basic_options(
 | 
				
			||||||
 | 
					        &browser_options, SUBGHZ_APP_FILENAME_EXTENSION, &I_sub1_10px);
 | 
				
			||||||
    browser_options.base_path = SUBGHZ_APP_FOLDER;
 | 
					    browser_options.base_path = SUBGHZ_APP_FOLDER;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Input events and views are managed by file_select
 | 
					    // Input events and views are managed by file_select
 | 
				
			||||||
@ -394,7 +395,7 @@ void subghz_file_name_clear(SubGhz* subghz) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool subghz_path_is_file(FuriString* path) {
 | 
					bool subghz_path_is_file(FuriString* path) {
 | 
				
			||||||
    return furi_string_end_with(path, SUBGHZ_APP_EXTENSION);
 | 
					    return furi_string_end_with(path, SUBGHZ_APP_FILENAME_EXTENSION);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void subghz_lock(SubGhz* subghz) {
 | 
					void subghz_lock(SubGhz* subghz) {
 | 
				
			||||||
 | 
				
			|||||||
@ -9,7 +9,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include <assets_icons.h>
 | 
					#include <assets_icons.h>
 | 
				
			||||||
#define SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE 100
 | 
					#define SUBGHZ_READ_RAW_RSSI_HISTORY_SIZE 100
 | 
				
			||||||
#define TAG "SubGhzReadRAW"
 | 
					#define TAG "SubGhzReadRaw"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct SubGhzReadRAW {
 | 
					struct SubGhzReadRAW {
 | 
				
			||||||
    View* view;
 | 
					    View* view;
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,7 @@
 | 
				
			|||||||
#include "hmac_sha256.h"
 | 
					#include "hmac_sha256.h"
 | 
				
			||||||
#include "micro-ecc/uECC.h"
 | 
					#include "micro-ecc/uECC.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define TAG "U2F"
 | 
					#define TAG "U2f"
 | 
				
			||||||
#define WORKER_TAG TAG "Worker"
 | 
					#define WORKER_TAG TAG "Worker"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define U2F_CMD_REGISTER 0x01
 | 
					#define U2F_CMD_REGISTER 0x01
 | 
				
			||||||
 | 
				
			|||||||
@ -5,7 +5,7 @@
 | 
				
			|||||||
#include <furi_hal_random.h>
 | 
					#include <furi_hal_random.h>
 | 
				
			||||||
#include <flipper_format/flipper_format.h>
 | 
					#include <flipper_format/flipper_format.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define TAG "U2F"
 | 
					#define TAG "U2f"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define U2F_DATA_FOLDER EXT_PATH("u2f/")
 | 
					#define U2F_DATA_FOLDER EXT_PATH("u2f/")
 | 
				
			||||||
#define U2F_CERT_FILE U2F_DATA_FOLDER "assets/cert.der"
 | 
					#define U2F_CERT_FILE U2F_DATA_FOLDER "assets/cert.der"
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include <furi_hal_console.h>
 | 
					#include <furi_hal_console.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define TAG "U2FHID"
 | 
					#define TAG "U2fHid"
 | 
				
			||||||
#define WORKER_TAG TAG "Worker"
 | 
					#define WORKER_TAG TAG "Worker"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define U2F_HID_MAX_PAYLOAD_LEN ((HID_U2F_PACKET_LEN - 7) + 128 * (HID_U2F_PACKET_LEN - 5))
 | 
					#define U2F_HID_MAX_PAYLOAD_LEN ((HID_U2F_PACKET_LEN - 7) + 128 * (HID_U2F_PACKET_LEN - 5))
 | 
				
			||||||
 | 
				
			|||||||
@ -101,7 +101,6 @@ static void desktop_clock_draw_callback(Canvas* canvas, void* context) {
 | 
				
			|||||||
    char buffer[20];
 | 
					    char buffer[20];
 | 
				
			||||||
    snprintf(buffer, sizeof(buffer), "%02u:%02u", hour, desktop->time_minute);
 | 
					    snprintf(buffer, sizeof(buffer), "%02u:%02u", hour, desktop->time_minute);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // TODO FL-3515: never do that, may cause visual glitches
 | 
					 | 
				
			||||||
    view_port_set_width(
 | 
					    view_port_set_width(
 | 
				
			||||||
        desktop->clock_viewport,
 | 
					        desktop->clock_viewport,
 | 
				
			||||||
        canvas_string_width(canvas, buffer) - 1 + (desktop->time_minute % 10 == 1));
 | 
					        canvas_string_width(canvas, buffer) - 1 + (desktop->time_minute % 10 == 1));
 | 
				
			||||||
@ -126,8 +125,6 @@ static bool desktop_custom_event_callback(void* context, uint32_t event) {
 | 
				
			|||||||
        return true;
 | 
					        return true;
 | 
				
			||||||
    case DesktopGlobalAfterAppFinished:
 | 
					    case DesktopGlobalAfterAppFinished:
 | 
				
			||||||
        animation_manager_load_and_continue_animation(desktop->animation_manager);
 | 
					        animation_manager_load_and_continue_animation(desktop->animation_manager);
 | 
				
			||||||
        // TODO FL-3497: Implement a message mechanism for loading settings and (optionally)
 | 
					 | 
				
			||||||
        // locking and unlocking
 | 
					 | 
				
			||||||
        DESKTOP_SETTINGS_LOAD(&desktop->settings);
 | 
					        DESKTOP_SETTINGS_LOAD(&desktop->settings);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        desktop_clock_reconfigure(desktop);
 | 
					        desktop_clock_reconfigure(desktop);
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include <furi.h>
 | 
					#include <furi.h>
 | 
				
			||||||
#include <furi_hal.h>
 | 
					#include <furi_hal.h>
 | 
				
			||||||
#include <furi_hal_rtc.h>
 | 
					 | 
				
			||||||
#include <stdint.h>
 | 
					#include <stdint.h>
 | 
				
			||||||
#include <u8g2_glue.h>
 | 
					#include <u8g2_glue.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -361,10 +361,11 @@ void gui_add_view_port(Gui* gui, ViewPort* view_port, GuiLayer layer) {
 | 
				
			|||||||
    furi_assert(view_port);
 | 
					    furi_assert(view_port);
 | 
				
			||||||
    furi_check(layer < GuiLayerMAX);
 | 
					    furi_check(layer < GuiLayerMAX);
 | 
				
			||||||
    // Only fullscreen supports Vertical orientation for now
 | 
					    // Only fullscreen supports Vertical orientation for now
 | 
				
			||||||
    furi_assert(
 | 
					    ViewPortOrientation view_port_orientation = view_port_get_orientation(view_port);
 | 
				
			||||||
 | 
					    furi_check(
 | 
				
			||||||
        (layer == GuiLayerFullscreen) ||
 | 
					        (layer == GuiLayerFullscreen) ||
 | 
				
			||||||
        ((view_port->orientation != ViewPortOrientationVertical) &&
 | 
					        ((view_port_orientation != ViewPortOrientationVertical) &&
 | 
				
			||||||
         (view_port->orientation != ViewPortOrientationVerticalFlip)));
 | 
					         (view_port_orientation != ViewPortOrientationVerticalFlip)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    gui_lock(gui);
 | 
					    gui_lock(gui);
 | 
				
			||||||
    // Verify that view port is not yet added
 | 
					    // Verify that view port is not yet added
 | 
				
			||||||
 | 
				
			|||||||
@ -272,7 +272,6 @@ void view_dispatcher_handle_input(ViewDispatcher* view_dispatcher, InputEvent* e
 | 
				
			|||||||
            } else if(view_dispatcher->navigation_event_callback) {
 | 
					            } else if(view_dispatcher->navigation_event_callback) {
 | 
				
			||||||
                // Dispatch navigation event
 | 
					                // Dispatch navigation event
 | 
				
			||||||
                if(!view_dispatcher->navigation_event_callback(view_dispatcher->event_context)) {
 | 
					                if(!view_dispatcher->navigation_event_callback(view_dispatcher->event_context)) {
 | 
				
			||||||
                    // TODO FL-3514: should we allow view_dispatcher to stop without navigation_event_callback?
 | 
					 | 
				
			||||||
                    view_dispatcher_stop(view_dispatcher);
 | 
					                    view_dispatcher_stop(view_dispatcher);
 | 
				
			||||||
                    return;
 | 
					                    return;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
				
			|||||||
@ -2,13 +2,10 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include <furi.h>
 | 
					#include <furi.h>
 | 
				
			||||||
#include <furi_hal.h>
 | 
					#include <furi_hal.h>
 | 
				
			||||||
#include <furi_hal_rtc.h>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "gui.h"
 | 
					#include "gui.h"
 | 
				
			||||||
#include "gui_i.h"
 | 
					#include "gui_i.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO FL-3498: add mutex to view_port ops
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
_Static_assert(ViewPortOrientationMAX == 4, "Incorrect ViewPortOrientation count");
 | 
					_Static_assert(ViewPortOrientationMAX == 4, "Incorrect ViewPortOrientation count");
 | 
				
			||||||
_Static_assert(
 | 
					_Static_assert(
 | 
				
			||||||
    (ViewPortOrientationHorizontal == 0 && ViewPortOrientationHorizontalFlip == 1 &&
 | 
					    (ViewPortOrientationHorizontal == 0 && ViewPortOrientationHorizontalFlip == 1 &&
 | 
				
			||||||
@ -95,52 +92,73 @@ ViewPort* view_port_alloc() {
 | 
				
			|||||||
    ViewPort* view_port = malloc(sizeof(ViewPort));
 | 
					    ViewPort* view_port = malloc(sizeof(ViewPort));
 | 
				
			||||||
    view_port->orientation = ViewPortOrientationHorizontal;
 | 
					    view_port->orientation = ViewPortOrientationHorizontal;
 | 
				
			||||||
    view_port->is_enabled = true;
 | 
					    view_port->is_enabled = true;
 | 
				
			||||||
 | 
					    view_port->mutex = furi_mutex_alloc(FuriMutexTypeRecursive);
 | 
				
			||||||
    return view_port;
 | 
					    return view_port;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void view_port_free(ViewPort* view_port) {
 | 
					void view_port_free(ViewPort* view_port) {
 | 
				
			||||||
    furi_assert(view_port);
 | 
					    furi_assert(view_port);
 | 
				
			||||||
 | 
					    furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk);
 | 
				
			||||||
    furi_check(view_port->gui == NULL);
 | 
					    furi_check(view_port->gui == NULL);
 | 
				
			||||||
 | 
					    furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk);
 | 
				
			||||||
 | 
					    furi_mutex_free(view_port->mutex);
 | 
				
			||||||
    free(view_port);
 | 
					    free(view_port);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void view_port_set_width(ViewPort* view_port, uint8_t width) {
 | 
					void view_port_set_width(ViewPort* view_port, uint8_t width) {
 | 
				
			||||||
    furi_assert(view_port);
 | 
					    furi_assert(view_port);
 | 
				
			||||||
 | 
					    furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk);
 | 
				
			||||||
    view_port->width = width;
 | 
					    view_port->width = width;
 | 
				
			||||||
 | 
					    furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
uint8_t view_port_get_width(const ViewPort* view_port) {
 | 
					uint8_t view_port_get_width(const ViewPort* view_port) {
 | 
				
			||||||
    furi_assert(view_port);
 | 
					    furi_assert(view_port);
 | 
				
			||||||
    return view_port->width;
 | 
					    furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk);
 | 
				
			||||||
 | 
					    uint8_t width = view_port->width;
 | 
				
			||||||
 | 
					    furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk);
 | 
				
			||||||
 | 
					    return width;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void view_port_set_height(ViewPort* view_port, uint8_t height) {
 | 
					void view_port_set_height(ViewPort* view_port, uint8_t height) {
 | 
				
			||||||
    furi_assert(view_port);
 | 
					    furi_assert(view_port);
 | 
				
			||||||
 | 
					    furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk);
 | 
				
			||||||
    view_port->height = height;
 | 
					    view_port->height = height;
 | 
				
			||||||
 | 
					    furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
uint8_t view_port_get_height(const ViewPort* view_port) {
 | 
					uint8_t view_port_get_height(const ViewPort* view_port) {
 | 
				
			||||||
    furi_assert(view_port);
 | 
					    furi_assert(view_port);
 | 
				
			||||||
    return view_port->height;
 | 
					    furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk);
 | 
				
			||||||
 | 
					    uint8_t height = view_port->height;
 | 
				
			||||||
 | 
					    furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk);
 | 
				
			||||||
 | 
					    return height;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void view_port_enabled_set(ViewPort* view_port, bool enabled) {
 | 
					void view_port_enabled_set(ViewPort* view_port, bool enabled) {
 | 
				
			||||||
    furi_assert(view_port);
 | 
					    furi_assert(view_port);
 | 
				
			||||||
 | 
					    furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk);
 | 
				
			||||||
    if(view_port->is_enabled != enabled) {
 | 
					    if(view_port->is_enabled != enabled) {
 | 
				
			||||||
        view_port->is_enabled = enabled;
 | 
					        view_port->is_enabled = enabled;
 | 
				
			||||||
        if(view_port->gui) gui_update(view_port->gui);
 | 
					        if(view_port->gui) gui_update(view_port->gui);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool view_port_is_enabled(const ViewPort* view_port) {
 | 
					bool view_port_is_enabled(const ViewPort* view_port) {
 | 
				
			||||||
    furi_assert(view_port);
 | 
					    furi_assert(view_port);
 | 
				
			||||||
    return view_port->is_enabled;
 | 
					    furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk);
 | 
				
			||||||
 | 
					    bool is_enabled = view_port->is_enabled;
 | 
				
			||||||
 | 
					    furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk);
 | 
				
			||||||
 | 
					    return is_enabled;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void view_port_draw_callback_set(ViewPort* view_port, ViewPortDrawCallback callback, void* context) {
 | 
					void view_port_draw_callback_set(ViewPort* view_port, ViewPortDrawCallback callback, void* context) {
 | 
				
			||||||
    furi_assert(view_port);
 | 
					    furi_assert(view_port);
 | 
				
			||||||
 | 
					    furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk);
 | 
				
			||||||
    view_port->draw_callback = callback;
 | 
					    view_port->draw_callback = callback;
 | 
				
			||||||
    view_port->draw_callback_context = context;
 | 
					    view_port->draw_callback_context = context;
 | 
				
			||||||
 | 
					    furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void view_port_input_callback_set(
 | 
					void view_port_input_callback_set(
 | 
				
			||||||
@ -148,34 +166,43 @@ void view_port_input_callback_set(
 | 
				
			|||||||
    ViewPortInputCallback callback,
 | 
					    ViewPortInputCallback callback,
 | 
				
			||||||
    void* context) {
 | 
					    void* context) {
 | 
				
			||||||
    furi_assert(view_port);
 | 
					    furi_assert(view_port);
 | 
				
			||||||
 | 
					    furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk);
 | 
				
			||||||
    view_port->input_callback = callback;
 | 
					    view_port->input_callback = callback;
 | 
				
			||||||
    view_port->input_callback_context = context;
 | 
					    view_port->input_callback_context = context;
 | 
				
			||||||
 | 
					    furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void view_port_update(ViewPort* view_port) {
 | 
					void view_port_update(ViewPort* view_port) {
 | 
				
			||||||
    furi_assert(view_port);
 | 
					    furi_assert(view_port);
 | 
				
			||||||
 | 
					    furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk);
 | 
				
			||||||
    if(view_port->gui && view_port->is_enabled) gui_update(view_port->gui);
 | 
					    if(view_port->gui && view_port->is_enabled) gui_update(view_port->gui);
 | 
				
			||||||
 | 
					    furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void view_port_gui_set(ViewPort* view_port, Gui* gui) {
 | 
					void view_port_gui_set(ViewPort* view_port, Gui* gui) {
 | 
				
			||||||
    furi_assert(view_port);
 | 
					    furi_assert(view_port);
 | 
				
			||||||
 | 
					    furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk);
 | 
				
			||||||
    view_port->gui = gui;
 | 
					    view_port->gui = gui;
 | 
				
			||||||
 | 
					    furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void view_port_draw(ViewPort* view_port, Canvas* canvas) {
 | 
					void view_port_draw(ViewPort* view_port, Canvas* canvas) {
 | 
				
			||||||
    furi_assert(view_port);
 | 
					    furi_assert(view_port);
 | 
				
			||||||
    furi_assert(canvas);
 | 
					    furi_assert(canvas);
 | 
				
			||||||
 | 
					    furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk);
 | 
				
			||||||
    furi_check(view_port->gui);
 | 
					    furi_check(view_port->gui);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(view_port->draw_callback) {
 | 
					    if(view_port->draw_callback) {
 | 
				
			||||||
        view_port_setup_canvas_orientation(view_port, canvas);
 | 
					        view_port_setup_canvas_orientation(view_port, canvas);
 | 
				
			||||||
        view_port->draw_callback(canvas, view_port->draw_callback_context);
 | 
					        view_port->draw_callback(canvas, view_port->draw_callback_context);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void view_port_input(ViewPort* view_port, InputEvent* event) {
 | 
					void view_port_input(ViewPort* view_port, InputEvent* event) {
 | 
				
			||||||
    furi_assert(view_port);
 | 
					    furi_assert(view_port);
 | 
				
			||||||
    furi_assert(event);
 | 
					    furi_assert(event);
 | 
				
			||||||
 | 
					    furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk);
 | 
				
			||||||
    furi_check(view_port->gui);
 | 
					    furi_check(view_port->gui);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(view_port->input_callback) {
 | 
					    if(view_port->input_callback) {
 | 
				
			||||||
@ -183,13 +210,19 @@ void view_port_input(ViewPort* view_port, InputEvent* event) {
 | 
				
			|||||||
        view_port_map_input(event, orientation);
 | 
					        view_port_map_input(event, orientation);
 | 
				
			||||||
        view_port->input_callback(event, view_port->input_callback_context);
 | 
					        view_port->input_callback(event, view_port->input_callback_context);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void view_port_set_orientation(ViewPort* view_port, ViewPortOrientation orientation) {
 | 
					void view_port_set_orientation(ViewPort* view_port, ViewPortOrientation orientation) {
 | 
				
			||||||
    furi_assert(view_port);
 | 
					    furi_assert(view_port);
 | 
				
			||||||
 | 
					    furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk);
 | 
				
			||||||
    view_port->orientation = orientation;
 | 
					    view_port->orientation = orientation;
 | 
				
			||||||
 | 
					    furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ViewPortOrientation view_port_get_orientation(const ViewPort* view_port) {
 | 
					ViewPortOrientation view_port_get_orientation(const ViewPort* view_port) {
 | 
				
			||||||
    return view_port->orientation;
 | 
					    furi_check(furi_mutex_acquire(view_port->mutex, FuriWaitForever) == FuriStatusOk);
 | 
				
			||||||
 | 
					    ViewPortOrientation orientation = view_port->orientation;
 | 
				
			||||||
 | 
					    furi_check(furi_mutex_release(view_port->mutex) == FuriStatusOk);
 | 
				
			||||||
 | 
					    return orientation;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -10,6 +10,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
struct ViewPort {
 | 
					struct ViewPort {
 | 
				
			||||||
    Gui* gui;
 | 
					    Gui* gui;
 | 
				
			||||||
 | 
					    FuriMutex* mutex;
 | 
				
			||||||
    bool is_enabled;
 | 
					    bool is_enabled;
 | 
				
			||||||
    ViewPortOrientation orientation;
 | 
					    ViewPortOrientation orientation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -30,7 +30,7 @@ void power_draw_battery_callback(Canvas* canvas, void* context) {
 | 
				
			|||||||
        if(power->state == PowerStateCharging) {
 | 
					        if(power->state == PowerStateCharging) {
 | 
				
			||||||
            canvas_set_bitmap_mode(canvas, 1);
 | 
					            canvas_set_bitmap_mode(canvas, 1);
 | 
				
			||||||
            canvas_set_color(canvas, ColorWhite);
 | 
					            canvas_set_color(canvas, ColorWhite);
 | 
				
			||||||
            // TODO FL-3510: replace -1 magic for uint8_t with re-framing
 | 
					            // -1 used here to overflow u8 number and render is outside of the area
 | 
				
			||||||
            canvas_draw_icon(canvas, 8, -1, &I_Charging_lightning_mask_9x10);
 | 
					            canvas_draw_icon(canvas, 8, -1, &I_Charging_lightning_mask_9x10);
 | 
				
			||||||
            canvas_set_color(canvas, ColorBlack);
 | 
					            canvas_set_color(canvas, ColorBlack);
 | 
				
			||||||
            canvas_draw_icon(canvas, 8, -1, &I_Charging_lightning_9x10);
 | 
					            canvas_draw_icon(canvas, 8, -1, &I_Charging_lightning_9x10);
 | 
				
			||||||
 | 
				
			|||||||
@ -15,6 +15,8 @@
 | 
				
			|||||||
#include <stdio.h>
 | 
					#include <stdio.h>
 | 
				
			||||||
#include <m-dict.h>
 | 
					#include <m-dict.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <bt/bt_service/bt.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define TAG "RpcSrv"
 | 
					#define TAG "RpcSrv"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef enum {
 | 
					typedef enum {
 | 
				
			||||||
@ -316,6 +318,15 @@ static int32_t rpc_session_worker(void* context) {
 | 
				
			|||||||
                    session->closed_callback(session->context);
 | 
					                    session->closed_callback(session->context);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                furi_mutex_release(session->callbacks_mutex);
 | 
					                furi_mutex_release(session->callbacks_mutex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if(session->owner == RpcOwnerBle) {
 | 
				
			||||||
 | 
					                    // Disconnect BLE session
 | 
				
			||||||
 | 
					                    FURI_LOG_E("RPC", "BLE session closed due to a decode error");
 | 
				
			||||||
 | 
					                    Bt* bt = furi_record_open(RECORD_BT);
 | 
				
			||||||
 | 
					                    bt_set_profile(bt, BtProfileSerial);
 | 
				
			||||||
 | 
					                    furi_record_close(RECORD_BT);
 | 
				
			||||||
 | 
					                    FURI_LOG_E("RPC", "Finished disconnecting the BLE session");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -242,6 +242,23 @@ static void rpc_system_storage_list_root(const PB_Main* request, void* context)
 | 
				
			|||||||
    rpc_send_and_release(session, &response);
 | 
					    rpc_send_and_release(session, &response);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool rpc_system_storage_list_filter(
 | 
				
			||||||
 | 
					    const PB_Storage_ListRequest* request,
 | 
				
			||||||
 | 
					    const FileInfo* fileinfo,
 | 
				
			||||||
 | 
					    const char* name) {
 | 
				
			||||||
 | 
					    bool result = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					        if(!path_contains_only_ascii(name)) break;
 | 
				
			||||||
 | 
					        if(request->filter_max_size) {
 | 
				
			||||||
 | 
					            if(fileinfo->size > request->filter_max_size) break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        result = true;
 | 
				
			||||||
 | 
					    } while(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void rpc_system_storage_list_process(const PB_Main* request, void* context) {
 | 
					static void rpc_system_storage_list_process(const PB_Main* request, void* context) {
 | 
				
			||||||
    furi_assert(request);
 | 
					    furi_assert(request);
 | 
				
			||||||
    furi_assert(context);
 | 
					    furi_assert(context);
 | 
				
			||||||
@ -253,9 +270,11 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex
 | 
				
			|||||||
    RpcSession* session = rpc_storage->session;
 | 
					    RpcSession* session = rpc_storage->session;
 | 
				
			||||||
    furi_assert(session);
 | 
					    furi_assert(session);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const PB_Storage_ListRequest* list_request = &request->content.storage_list_request;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    rpc_system_storage_reset_state(rpc_storage, session, true);
 | 
					    rpc_system_storage_reset_state(rpc_storage, session, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(!strcmp(request->content.storage_list_request.path, "/")) {
 | 
					    if(!strcmp(list_request->path, "/")) {
 | 
				
			||||||
        rpc_system_storage_list_root(request, context);
 | 
					        rpc_system_storage_list_root(request, context);
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -271,7 +290,7 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
    PB_Storage_ListResponse* list = &response.content.storage_list_response;
 | 
					    PB_Storage_ListResponse* list = &response.content.storage_list_response;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    bool include_md5 = request->content.storage_list_request.include_md5;
 | 
					    bool include_md5 = list_request->include_md5;
 | 
				
			||||||
    FuriString* md5 = furi_string_alloc();
 | 
					    FuriString* md5 = furi_string_alloc();
 | 
				
			||||||
    FuriString* md5_path = furi_string_alloc();
 | 
					    FuriString* md5_path = furi_string_alloc();
 | 
				
			||||||
    File* file = storage_file_alloc(fs_api);
 | 
					    File* file = storage_file_alloc(fs_api);
 | 
				
			||||||
@ -279,7 +298,7 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex
 | 
				
			|||||||
    bool finish = false;
 | 
					    bool finish = false;
 | 
				
			||||||
    int i = 0;
 | 
					    int i = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(!storage_dir_open(dir, request->content.storage_list_request.path)) {
 | 
					    if(!storage_dir_open(dir, list_request->path)) {
 | 
				
			||||||
        response.command_status = rpc_system_storage_get_file_error(dir);
 | 
					        response.command_status = rpc_system_storage_get_file_error(dir);
 | 
				
			||||||
        response.which_content = PB_Main_empty_tag;
 | 
					        response.which_content = PB_Main_empty_tag;
 | 
				
			||||||
        finish = true;
 | 
					        finish = true;
 | 
				
			||||||
@ -289,7 +308,7 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex
 | 
				
			|||||||
        FileInfo fileinfo;
 | 
					        FileInfo fileinfo;
 | 
				
			||||||
        char* name = malloc(MAX_NAME_LENGTH + 1);
 | 
					        char* name = malloc(MAX_NAME_LENGTH + 1);
 | 
				
			||||||
        if(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) {
 | 
					        if(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) {
 | 
				
			||||||
            if(path_contains_only_ascii(name)) {
 | 
					            if(rpc_system_storage_list_filter(list_request, &fileinfo, name)) {
 | 
				
			||||||
                if(i == COUNT_OF(list->file)) {
 | 
					                if(i == COUNT_OF(list->file)) {
 | 
				
			||||||
                    list->file_count = i;
 | 
					                    list->file_count = i;
 | 
				
			||||||
                    response.has_next = true;
 | 
					                    response.has_next = true;
 | 
				
			||||||
@ -303,11 +322,7 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex
 | 
				
			|||||||
                list->file[i].name = name;
 | 
					                list->file[i].name = name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if(include_md5 && !file_info_is_dir(&fileinfo)) {
 | 
					                if(include_md5 && !file_info_is_dir(&fileinfo)) {
 | 
				
			||||||
                    furi_string_printf( //-V576
 | 
					                    furi_string_printf(md5_path, "%s/%s", list_request->path, name); //-V576
 | 
				
			||||||
                        md5_path,
 | 
					 | 
				
			||||||
                        "%s/%s",
 | 
					 | 
				
			||||||
                        request->content.storage_list_request.path,
 | 
					 | 
				
			||||||
                        name);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if(md5_string_calc_file(file, furi_string_get_cstr(md5_path), md5, NULL)) {
 | 
					                    if(md5_string_calc_file(file, furi_string_get_cstr(md5_path), md5, NULL)) {
 | 
				
			||||||
                        char* md5sum = list->file[i].md5sum;
 | 
					                        char* md5sum = list->file[i].md5sum;
 | 
				
			||||||
 | 
				
			|||||||
@ -12,7 +12,7 @@
 | 
				
			|||||||
#define ICON_SD_MOUNTED &I_SDcardMounted_11x8
 | 
					#define ICON_SD_MOUNTED &I_SDcardMounted_11x8
 | 
				
			||||||
#define ICON_SD_ERROR &I_SDcardFail_11x8
 | 
					#define ICON_SD_ERROR &I_SDcardFail_11x8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define TAG RECORD_STORAGE
 | 
					#define TAG "Storage"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void storage_app_sd_icon_draw_callback(Canvas* canvas, void* context) {
 | 
					static void storage_app_sd_icon_draw_callback(Canvas* canvas, void* context) {
 | 
				
			||||||
    furi_assert(canvas);
 | 
					    furi_assert(canvas);
 | 
				
			||||||
 | 
				
			|||||||
@ -334,12 +334,20 @@ const char* storage_file_get_error_desc(File* file);
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
FS_Error storage_sd_format(Storage* api);
 | 
					FS_Error storage_sd_format(Storage* api);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Will unmount the SD card
 | 
					/** Will unmount the SD card.
 | 
				
			||||||
 | 
					 * Will return FSE_NOT_READY if the SD card is not mounted.
 | 
				
			||||||
 | 
					 * Will return FSE_DENIED if there are open files on the SD card.
 | 
				
			||||||
 * @param api pointer to the api
 | 
					 * @param api pointer to the api
 | 
				
			||||||
 * @return FS_Error operation result
 | 
					 * @return FS_Error operation result
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
FS_Error storage_sd_unmount(Storage* api);
 | 
					FS_Error storage_sd_unmount(Storage* api);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Will mount the SD card
 | 
				
			||||||
 | 
					 * @param api pointer to the api
 | 
				
			||||||
 | 
					 * @return FS_Error operation result
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					FS_Error storage_sd_mount(Storage* api);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Retrieves SD card information
 | 
					/** Retrieves SD card information
 | 
				
			||||||
 * @param api pointer to the api
 | 
					 * @param api pointer to the api
 | 
				
			||||||
 * @param info pointer to the info
 | 
					 * @param info pointer to the info
 | 
				
			||||||
 | 
				
			|||||||
@ -11,7 +11,7 @@
 | 
				
			|||||||
#define MAX_EXT_LEN 16
 | 
					#define MAX_EXT_LEN 16
 | 
				
			||||||
#define FILE_BUFFER_SIZE 512
 | 
					#define FILE_BUFFER_SIZE 512
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define TAG "StorageAPI"
 | 
					#define TAG "StorageApi"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define S_API_PROLOGUE FuriApiLock lock = api_lock_alloc_locked();
 | 
					#define S_API_PROLOGUE FuriApiLock lock = api_lock_alloc_locked();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -781,6 +781,14 @@ FS_Error storage_sd_unmount(Storage* storage) {
 | 
				
			|||||||
    return S_RETURN_ERROR;
 | 
					    return S_RETURN_ERROR;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FS_Error storage_sd_mount(Storage* storage) {
 | 
				
			||||||
 | 
					    S_API_PROLOGUE;
 | 
				
			||||||
 | 
					    SAData data = {};
 | 
				
			||||||
 | 
					    S_API_MESSAGE(StorageCommandSDMount);
 | 
				
			||||||
 | 
					    S_API_EPILOGUE;
 | 
				
			||||||
 | 
					    return S_RETURN_ERROR;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FS_Error storage_sd_info(Storage* storage, SDInfo* info) {
 | 
					FS_Error storage_sd_info(Storage* storage, SDInfo* info) {
 | 
				
			||||||
    S_API_PROLOGUE;
 | 
					    S_API_PROLOGUE;
 | 
				
			||||||
    SAData data = {
 | 
					    SAData data = {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,8 @@
 | 
				
			|||||||
#include "storage_glue.h"
 | 
					#include "storage_glue.h"
 | 
				
			||||||
#include <furi_hal.h>
 | 
					#include <furi_hal.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define TAG "StorageGlue"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/****************** storage file ******************/
 | 
					/****************** storage file ******************/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void storage_file_init(StorageFile* obj) {
 | 
					void storage_file_init(StorageFile* obj) {
 | 
				
			||||||
@ -149,3 +151,8 @@ bool storage_pop_storage_file(File* file, StorageData* storage) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return result;
 | 
					    return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					size_t storage_open_files_count(StorageData* storage) {
 | 
				
			||||||
 | 
					    size_t count = StorageFileList_size(storage->files);
 | 
				
			||||||
 | 
					    return count;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -68,6 +68,8 @@ void* storage_get_storage_file_data(const File* file, StorageData* storage);
 | 
				
			|||||||
void storage_push_storage_file(File* file, FuriString* path, StorageData* storage);
 | 
					void storage_push_storage_file(File* file, FuriString* path, StorageData* storage);
 | 
				
			||||||
bool storage_pop_storage_file(File* file, StorageData* storage);
 | 
					bool storage_pop_storage_file(File* file, StorageData* storage);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					size_t storage_open_files_count(StorageData* storage);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef __cplusplus
 | 
					#ifdef __cplusplus
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
				
			|||||||
@ -141,6 +141,7 @@ typedef enum {
 | 
				
			|||||||
    StorageCommandSDInfo,
 | 
					    StorageCommandSDInfo,
 | 
				
			||||||
    StorageCommandSDStatus,
 | 
					    StorageCommandSDStatus,
 | 
				
			||||||
    StorageCommandCommonResolvePath,
 | 
					    StorageCommandCommonResolvePath,
 | 
				
			||||||
 | 
					    StorageCommandSDMount,
 | 
				
			||||||
} StorageCommand;
 | 
					} StorageCommand;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct {
 | 
					typedef struct {
 | 
				
			||||||
 | 
				
			|||||||
@ -418,13 +418,39 @@ static FS_Error storage_process_sd_format(Storage* app) {
 | 
				
			|||||||
static FS_Error storage_process_sd_unmount(Storage* app) {
 | 
					static FS_Error storage_process_sd_unmount(Storage* app) {
 | 
				
			||||||
    FS_Error ret = FSE_OK;
 | 
					    FS_Error ret = FSE_OK;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(storage_data_status(&app->storage[ST_EXT]) == StorageStatusNotReady) {
 | 
					    do {
 | 
				
			||||||
 | 
					        StorageData* storage = &app->storage[ST_EXT];
 | 
				
			||||||
 | 
					        if(storage_data_status(storage) == StorageStatusNotReady) {
 | 
				
			||||||
            ret = FSE_NOT_READY;
 | 
					            ret = FSE_NOT_READY;
 | 
				
			||||||
    } else {
 | 
					            break;
 | 
				
			||||||
        sd_unmount_card(&app->storage[ST_EXT]);
 | 
					 | 
				
			||||||
        storage_data_timestamp(&app->storage[ST_EXT]);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(storage_open_files_count(storage)) {
 | 
				
			||||||
 | 
					            ret = FSE_DENIED;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sd_unmount_card(storage);
 | 
				
			||||||
 | 
					        storage_data_timestamp(storage);
 | 
				
			||||||
 | 
					    } while(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static FS_Error storage_process_sd_mount(Storage* app) {
 | 
				
			||||||
 | 
					    FS_Error ret = FSE_OK;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					        StorageData* storage = &app->storage[ST_EXT];
 | 
				
			||||||
 | 
					        if(storage_data_status(storage) != StorageStatusNotReady) {
 | 
				
			||||||
 | 
					            ret = FSE_NOT_READY;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ret = sd_mount_card(storage, true);
 | 
				
			||||||
 | 
					        storage_data_timestamp(storage);
 | 
				
			||||||
 | 
					    } while(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return ret;
 | 
					    return ret;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -630,6 +656,9 @@ void storage_process_message_internal(Storage* app, StorageMessage* message) {
 | 
				
			|||||||
    case StorageCommandSDUnmount:
 | 
					    case StorageCommandSDUnmount:
 | 
				
			||||||
        message->return_data->error_value = storage_process_sd_unmount(app);
 | 
					        message->return_data->error_value = storage_process_sd_unmount(app);
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
 | 
					    case StorageCommandSDMount:
 | 
				
			||||||
 | 
					        message->return_data->error_value = storage_process_sd_mount(app);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
    case StorageCommandSDInfo:
 | 
					    case StorageCommandSDInfo:
 | 
				
			||||||
        message->return_data->error_value =
 | 
					        message->return_data->error_value =
 | 
				
			||||||
            storage_process_sd_info(app, message->data->sdinfo.info);
 | 
					            storage_process_sd_info(app, message->data->sdinfo.info);
 | 
				
			||||||
 | 
				
			|||||||
@ -32,8 +32,6 @@ typedef struct {
 | 
				
			|||||||
    uint32_t product_serial_number;
 | 
					    uint32_t product_serial_number;
 | 
				
			||||||
    uint8_t manufacturing_month;
 | 
					    uint8_t manufacturing_month;
 | 
				
			||||||
    uint16_t manufacturing_year;
 | 
					    uint16_t manufacturing_year;
 | 
				
			||||||
 | 
					 | 
				
			||||||
    FS_Error error;
 | 
					 | 
				
			||||||
} SDInfo;
 | 
					} SDInfo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const char* sd_api_get_fs_type_text(SDFsType fs_type);
 | 
					const char* sd_api_get_fs_type_text(SDFsType fs_type);
 | 
				
			||||||
 | 
				
			|||||||
@ -24,13 +24,13 @@ static FS_Error storage_ext_parse_error(SDError error);
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/******************* Core Functions *******************/
 | 
					/******************* Core Functions *******************/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static bool sd_mount_card(StorageData* storage, bool notify) {
 | 
					static bool sd_mount_card_internal(StorageData* storage, bool notify) {
 | 
				
			||||||
    bool result = false;
 | 
					    bool result = false;
 | 
				
			||||||
    uint8_t counter = sd_max_mount_retry_count();
 | 
					    uint8_t counter = furi_hal_sd_max_mount_retry_count();
 | 
				
			||||||
    uint8_t bsp_result;
 | 
					    uint8_t bsp_result;
 | 
				
			||||||
    SDData* sd_data = storage->data;
 | 
					    SDData* sd_data = storage->data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    while(result == false && counter > 0 && hal_sd_detect()) {
 | 
					    while(result == false && counter > 0 && furi_hal_sd_is_present()) {
 | 
				
			||||||
        if(notify) {
 | 
					        if(notify) {
 | 
				
			||||||
            NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
 | 
					            NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
 | 
				
			||||||
            sd_notify_wait(notification);
 | 
					            sd_notify_wait(notification);
 | 
				
			||||||
@ -39,9 +39,9 @@ static bool sd_mount_card(StorageData* storage, bool notify) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if((counter % 2) == 0) {
 | 
					        if((counter % 2) == 0) {
 | 
				
			||||||
            // power reset sd card
 | 
					            // power reset sd card
 | 
				
			||||||
            bsp_result = sd_init(true);
 | 
					            bsp_result = furi_hal_sd_init(true);
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            bsp_result = sd_init(false);
 | 
					            bsp_result = furi_hal_sd_init(false);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(bsp_result) {
 | 
					        if(bsp_result) {
 | 
				
			||||||
@ -106,6 +106,32 @@ FS_Error sd_unmount_card(StorageData* storage) {
 | 
				
			|||||||
    return storage_ext_parse_error(error);
 | 
					    return storage_ext_parse_error(error);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FS_Error sd_mount_card(StorageData* storage, bool notify) {
 | 
				
			||||||
 | 
					    sd_mount_card_internal(storage, notify);
 | 
				
			||||||
 | 
					    FS_Error error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(storage->status != StorageStatusOK) {
 | 
				
			||||||
 | 
					        FURI_LOG_E(TAG, "sd init error: %s", storage_data_status_text(storage));
 | 
				
			||||||
 | 
					        if(notify) {
 | 
				
			||||||
 | 
					            NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
 | 
				
			||||||
 | 
					            sd_notify_error(notification);
 | 
				
			||||||
 | 
					            furi_record_close(RECORD_NOTIFICATION);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        error = FSE_INTERNAL;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        FURI_LOG_I(TAG, "card mounted");
 | 
				
			||||||
 | 
					        if(notify) {
 | 
				
			||||||
 | 
					            NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
 | 
				
			||||||
 | 
					            sd_notify_success(notification);
 | 
				
			||||||
 | 
					            furi_record_close(RECORD_NOTIFICATION);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        error = FSE_OK;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return error;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FS_Error sd_format_card(StorageData* storage) {
 | 
					FS_Error sd_format_card(StorageData* storage) {
 | 
				
			||||||
#ifdef FURI_RAM_EXEC
 | 
					#ifdef FURI_RAM_EXEC
 | 
				
			||||||
    UNUSED(storage);
 | 
					    UNUSED(storage);
 | 
				
			||||||
@ -199,18 +225,18 @@ FS_Error sd_card_info(StorageData* storage, SDInfo* sd_info) {
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    SD_CID cid;
 | 
					    FuriHalSdInfo info;
 | 
				
			||||||
    SdSpiStatus status = sd_get_cid(&cid);
 | 
					    FuriStatus status = furi_hal_sd_info(&info);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(status == SdSpiStatusOK) {
 | 
					    if(status == FuriStatusOk) {
 | 
				
			||||||
        sd_info->manufacturer_id = cid.ManufacturerID;
 | 
					        sd_info->manufacturer_id = info.manufacturer_id;
 | 
				
			||||||
        memcpy(sd_info->oem_id, cid.OEM_AppliID, sizeof(cid.OEM_AppliID));
 | 
					        memcpy(sd_info->oem_id, info.oem_id, sizeof(info.oem_id));
 | 
				
			||||||
        memcpy(sd_info->product_name, cid.ProdName, sizeof(cid.ProdName));
 | 
					        memcpy(sd_info->product_name, info.product_name, sizeof(info.product_name));
 | 
				
			||||||
        sd_info->product_revision_major = cid.ProdRev >> 4;
 | 
					        sd_info->product_revision_major = info.product_revision_major;
 | 
				
			||||||
        sd_info->product_revision_minor = cid.ProdRev & 0x0F;
 | 
					        sd_info->product_revision_minor = info.product_revision_minor;
 | 
				
			||||||
        sd_info->product_serial_number = cid.ProdSN;
 | 
					        sd_info->product_serial_number = info.product_serial_number;
 | 
				
			||||||
        sd_info->manufacturing_year = 2000 + cid.ManufactYear;
 | 
					        sd_info->manufacturing_year = info.manufacturing_year;
 | 
				
			||||||
        sd_info->manufacturing_month = cid.ManufactMonth;
 | 
					        sd_info->manufacturing_month = info.manufacturing_month;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return storage_ext_parse_error(error);
 | 
					    return storage_ext_parse_error(error);
 | 
				
			||||||
@ -220,36 +246,19 @@ static void storage_ext_tick_internal(StorageData* storage, bool notify) {
 | 
				
			|||||||
    SDData* sd_data = storage->data;
 | 
					    SDData* sd_data = storage->data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(sd_data->sd_was_present) {
 | 
					    if(sd_data->sd_was_present) {
 | 
				
			||||||
        if(hal_sd_detect()) {
 | 
					        if(furi_hal_sd_is_present()) {
 | 
				
			||||||
            FURI_LOG_I(TAG, "card detected");
 | 
					            FURI_LOG_I(TAG, "card detected");
 | 
				
			||||||
 | 
					            sd_data->sd_was_present = false;
 | 
				
			||||||
            sd_mount_card(storage, notify);
 | 
					            sd_mount_card(storage, notify);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if(storage->status != StorageStatusOK) {
 | 
					            if(!furi_hal_sd_is_present()) {
 | 
				
			||||||
                FURI_LOG_E(TAG, "sd init error: %s", storage_data_status_text(storage));
 | 
					 | 
				
			||||||
                if(notify) {
 | 
					 | 
				
			||||||
                    NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
 | 
					 | 
				
			||||||
                    sd_notify_error(notification);
 | 
					 | 
				
			||||||
                    furi_record_close(RECORD_NOTIFICATION);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                FURI_LOG_I(TAG, "card mounted");
 | 
					 | 
				
			||||||
                if(notify) {
 | 
					 | 
				
			||||||
                    NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
 | 
					 | 
				
			||||||
                    sd_notify_success(notification);
 | 
					 | 
				
			||||||
                    furi_record_close(RECORD_NOTIFICATION);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            sd_data->sd_was_present = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if(!hal_sd_detect()) {
 | 
					 | 
				
			||||||
                FURI_LOG_I(TAG, "card removed while mounting");
 | 
					                FURI_LOG_I(TAG, "card removed while mounting");
 | 
				
			||||||
                sd_unmount_card(storage);
 | 
					                sd_unmount_card(storage);
 | 
				
			||||||
                sd_data->sd_was_present = true;
 | 
					                sd_data->sd_was_present = true;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        if(!hal_sd_detect()) {
 | 
					        if(!furi_hal_sd_is_present()) {
 | 
				
			||||||
            FURI_LOG_I(TAG, "card removed");
 | 
					            FURI_LOG_I(TAG, "card removed");
 | 
				
			||||||
            sd_data->sd_was_present = true;
 | 
					            sd_data->sd_was_present = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -630,7 +639,7 @@ void storage_ext_init(StorageData* storage) {
 | 
				
			|||||||
    storage->api.tick = storage_ext_tick;
 | 
					    storage->api.tick = storage_ext_tick;
 | 
				
			||||||
    storage->fs_api = &fs_api;
 | 
					    storage->fs_api = &fs_api;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    hal_sd_detect_init();
 | 
					    furi_hal_sd_presence_init();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // do not notify on first launch, notifications app is waiting for our thread to read settings
 | 
					    // do not notify on first launch, notifications app is waiting for our thread to read settings
 | 
				
			||||||
    storage_ext_tick_internal(storage, false);
 | 
					    storage_ext_tick_internal(storage, false);
 | 
				
			||||||
 | 
				
			|||||||
@ -8,6 +8,7 @@ extern "C" {
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void storage_ext_init(StorageData* storage);
 | 
					void storage_ext_init(StorageData* storage);
 | 
				
			||||||
 | 
					FS_Error sd_mount_card(StorageData* storage, bool notify);
 | 
				
			||||||
FS_Error sd_unmount_card(StorageData* storage);
 | 
					FS_Error sd_unmount_card(StorageData* storage);
 | 
				
			||||||
FS_Error sd_format_card(StorageData* storage);
 | 
					FS_Error sd_format_card(StorageData* storage);
 | 
				
			||||||
FS_Error sd_card_info(StorageData* storage, SDInfo* sd_info);
 | 
					FS_Error sd_card_info(StorageData* storage, SDInfo* sd_info);
 | 
				
			||||||
 | 
				
			|||||||
@ -31,12 +31,24 @@ void storage_settings_scene_start_on_enter(void* context) {
 | 
				
			|||||||
        StorageSettingsStartSubmenuIndexSDInfo,
 | 
					        StorageSettingsStartSubmenuIndexSDInfo,
 | 
				
			||||||
        storage_settings_scene_start_submenu_callback,
 | 
					        storage_settings_scene_start_submenu_callback,
 | 
				
			||||||
        app);
 | 
					        app);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FS_Error sd_status = storage_sd_status(app->fs_api);
 | 
				
			||||||
 | 
					    if(sd_status != FSE_OK) {
 | 
				
			||||||
 | 
					        submenu_add_item(
 | 
				
			||||||
 | 
					            submenu,
 | 
				
			||||||
 | 
					            "Mount SD Card",
 | 
				
			||||||
 | 
					            StorageSettingsStartSubmenuIndexUnmount,
 | 
				
			||||||
 | 
					            storage_settings_scene_start_submenu_callback,
 | 
				
			||||||
 | 
					            app);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
        submenu_add_item(
 | 
					        submenu_add_item(
 | 
				
			||||||
            submenu,
 | 
					            submenu,
 | 
				
			||||||
            "Unmount SD Card",
 | 
					            "Unmount SD Card",
 | 
				
			||||||
            StorageSettingsStartSubmenuIndexUnmount,
 | 
					            StorageSettingsStartSubmenuIndexUnmount,
 | 
				
			||||||
            storage_settings_scene_start_submenu_callback,
 | 
					            storage_settings_scene_start_submenu_callback,
 | 
				
			||||||
            app);
 | 
					            app);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    submenu_add_item(
 | 
					    submenu_add_item(
 | 
				
			||||||
        submenu,
 | 
					        submenu,
 | 
				
			||||||
        "Format SD Card",
 | 
					        "Format SD Card",
 | 
				
			||||||
 | 
				
			|||||||
@ -12,13 +12,17 @@ void storage_settings_scene_unmount_confirm_on_enter(void* context) {
 | 
				
			|||||||
    DialogEx* dialog_ex = app->dialog_ex;
 | 
					    DialogEx* dialog_ex = app->dialog_ex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    FS_Error sd_status = storage_sd_status(app->fs_api);
 | 
					    FS_Error sd_status = storage_sd_status(app->fs_api);
 | 
				
			||||||
 | 
					 | 
				
			||||||
    if(sd_status == FSE_NOT_READY) {
 | 
					    if(sd_status == FSE_NOT_READY) {
 | 
				
			||||||
        dialog_ex_set_icon(dialog_ex, 72, 17, &I_DolphinCommon_56x48);
 | 
					        dialog_ex_set_header(dialog_ex, "Mount SD Card?", 64, 10, AlignCenter, AlignCenter);
 | 
				
			||||||
        dialog_ex_set_header(dialog_ex, "SD Card Not Mounted", 64, 3, AlignCenter, AlignTop);
 | 
					 | 
				
			||||||
        dialog_ex_set_text(
 | 
					        dialog_ex_set_text(
 | 
				
			||||||
            dialog_ex, "Try to reinsert\nor format SD\ncard.", 3, 19, AlignLeft, AlignTop);
 | 
					            dialog_ex,
 | 
				
			||||||
        dialog_ex_set_center_button_text(dialog_ex, "Ok");
 | 
					            "This may turn off power\nfor external modules",
 | 
				
			||||||
 | 
					            64,
 | 
				
			||||||
 | 
					            32,
 | 
				
			||||||
 | 
					            AlignCenter,
 | 
				
			||||||
 | 
					            AlignCenter);
 | 
				
			||||||
 | 
					        dialog_ex_set_left_button_text(dialog_ex, "Cancel");
 | 
				
			||||||
 | 
					        dialog_ex_set_right_button_text(dialog_ex, "Mount");
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        dialog_ex_set_header(dialog_ex, "Unmount SD Card?", 64, 10, AlignCenter, AlignCenter);
 | 
					        dialog_ex_set_header(dialog_ex, "Unmount SD Card?", 64, 10, AlignCenter, AlignCenter);
 | 
				
			||||||
        dialog_ex_set_text(
 | 
					        dialog_ex_set_text(
 | 
				
			||||||
 | 
				
			|||||||
@ -9,22 +9,41 @@ static void
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void storage_settings_scene_unmounted_on_enter(void* context) {
 | 
					void storage_settings_scene_unmounted_on_enter(void* context) {
 | 
				
			||||||
    StorageSettings* app = context;
 | 
					    StorageSettings* app = context;
 | 
				
			||||||
    FS_Error error = storage_sd_unmount(app->fs_api);
 | 
					 | 
				
			||||||
    DialogEx* dialog_ex = app->dialog_ex;
 | 
					    DialogEx* dialog_ex = app->dialog_ex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FS_Error sd_status = storage_sd_status(app->fs_api);
 | 
				
			||||||
 | 
					    if(sd_status == FSE_NOT_READY) {
 | 
				
			||||||
 | 
					        FS_Error error = storage_sd_mount(app->fs_api);
 | 
				
			||||||
 | 
					        if(error == FSE_OK) {
 | 
				
			||||||
 | 
					            dialog_ex_set_header(dialog_ex, "SD Card Mounted", 64, 3, AlignCenter, AlignTop);
 | 
				
			||||||
 | 
					            dialog_ex_set_text(
 | 
				
			||||||
 | 
					                dialog_ex, "Flipper can use\nSD card now.", 3, 22, AlignLeft, AlignTop);
 | 
				
			||||||
 | 
					            notification_message(app->notification, &sequence_blink_green_100);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            dialog_ex_set_header(dialog_ex, "Cannot Mount SD Card", 64, 3, AlignCenter, AlignTop);
 | 
				
			||||||
 | 
					            dialog_ex_set_text(
 | 
				
			||||||
 | 
					                dialog_ex, storage_error_get_desc(error), 3, 22, AlignLeft, AlignTop);
 | 
				
			||||||
 | 
					            notification_message(app->notification, &sequence_blink_red_100);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        FS_Error error = storage_sd_unmount(app->fs_api);
 | 
				
			||||||
 | 
					        if(error == FSE_OK) {
 | 
				
			||||||
 | 
					            dialog_ex_set_header(dialog_ex, "SD Card Unmounted", 64, 3, AlignCenter, AlignTop);
 | 
				
			||||||
 | 
					            dialog_ex_set_text(
 | 
				
			||||||
 | 
					                dialog_ex, "You can remove\nSD card now.", 3, 22, AlignLeft, AlignTop);
 | 
				
			||||||
 | 
					            notification_message(app->notification, &sequence_blink_green_100);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            dialog_ex_set_header(
 | 
				
			||||||
 | 
					                dialog_ex, "Cannot Unmount SD Card", 64, 3, AlignCenter, AlignTop);
 | 
				
			||||||
 | 
					            dialog_ex_set_text(
 | 
				
			||||||
 | 
					                dialog_ex, storage_error_get_desc(error), 3, 22, AlignLeft, AlignTop);
 | 
				
			||||||
 | 
					            notification_message(app->notification, &sequence_blink_red_100);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dialog_ex_set_center_button_text(dialog_ex, "OK");
 | 
					    dialog_ex_set_center_button_text(dialog_ex, "OK");
 | 
				
			||||||
    dialog_ex_set_icon(dialog_ex, 72, 17, &I_DolphinCommon_56x48);
 | 
					    dialog_ex_set_icon(dialog_ex, 72, 17, &I_DolphinCommon_56x48);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(error == FSE_OK) {
 | 
					 | 
				
			||||||
        dialog_ex_set_header(dialog_ex, "SD Card Unmounted", 64, 3, AlignCenter, AlignTop);
 | 
					 | 
				
			||||||
        dialog_ex_set_text(dialog_ex, "You can remove\nSD card now.", 3, 22, AlignLeft, AlignTop);
 | 
					 | 
				
			||||||
        notification_message(app->notification, &sequence_blink_green_100);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        dialog_ex_set_header(dialog_ex, "Cannot Unmount SD Card", 64, 3, AlignCenter, AlignTop);
 | 
					 | 
				
			||||||
        dialog_ex_set_text(dialog_ex, storage_error_get_desc(error), 3, 22, AlignLeft, AlignTop);
 | 
					 | 
				
			||||||
        notification_message(app->notification, &sequence_blink_red_100);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dialog_ex_set_context(dialog_ex, app);
 | 
					    dialog_ex_set_context(dialog_ex, app);
 | 
				
			||||||
    dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_unmounted_dialog_callback);
 | 
					    dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_unmounted_dialog_callback);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -153,6 +153,21 @@ static void sleep_method_changed(VariableItem* item) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const char* const filename_scheme[] = {
 | 
				
			||||||
 | 
					    "Default",
 | 
				
			||||||
 | 
					    "Detailed",
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void filename_scheme_changed(VariableItem* item) {
 | 
				
			||||||
 | 
					    uint8_t index = variable_item_get_current_value_index(item);
 | 
				
			||||||
 | 
					    variable_item_set_current_value_text(item, filename_scheme[index]);
 | 
				
			||||||
 | 
					    if(index) {
 | 
				
			||||||
 | 
					        furi_hal_rtc_set_flag(FuriHalRtcFlagDetailedFilename);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        furi_hal_rtc_reset_flag(FuriHalRtcFlagDetailedFilename);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static uint32_t system_settings_exit(void* context) {
 | 
					static uint32_t system_settings_exit(void* context) {
 | 
				
			||||||
    UNUSED(context);
 | 
					    UNUSED(context);
 | 
				
			||||||
    return VIEW_NONE;
 | 
					    return VIEW_NONE;
 | 
				
			||||||
@ -236,6 +251,12 @@ SystemSettings* system_settings_alloc() {
 | 
				
			|||||||
    variable_item_set_current_value_index(item, value_index);
 | 
					    variable_item_set_current_value_index(item, value_index);
 | 
				
			||||||
    variable_item_set_current_value_text(item, sleep_method[value_index]);
 | 
					    variable_item_set_current_value_text(item, sleep_method[value_index]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    item = variable_item_list_add(
 | 
				
			||||||
 | 
					        app->var_item_list, "File Naming", COUNT_OF(filename_scheme), filename_scheme_changed, app);
 | 
				
			||||||
 | 
					    value_index = furi_hal_rtc_is_flag_set(FuriHalRtcFlagDetailedFilename) ? 1 : 0;
 | 
				
			||||||
 | 
					    variable_item_set_current_value_index(item, value_index);
 | 
				
			||||||
 | 
					    variable_item_set_current_value_text(item, filename_scheme[value_index]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    view_set_previous_callback(
 | 
					    view_set_previous_callback(
 | 
				
			||||||
        variable_item_list_get_view(app->var_item_list), system_settings_exit);
 | 
					        variable_item_list_get_view(app->var_item_list), system_settings_exit);
 | 
				
			||||||
    view_dispatcher_add_view(
 | 
					    view_dispatcher_add_view(
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										28
									
								
								applications/system/hid_app/application.fam
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					App(
 | 
				
			||||||
 | 
					    appid="hid_usb",
 | 
				
			||||||
 | 
					    name="Remote",
 | 
				
			||||||
 | 
					    apptype=FlipperAppType.EXTERNAL,
 | 
				
			||||||
 | 
					    entry_point="hid_usb_app",
 | 
				
			||||||
 | 
					    stack_size=1 * 1024,
 | 
				
			||||||
 | 
					    fap_description="Use Flipper as a HID remote control over USB",
 | 
				
			||||||
 | 
					    fap_version="1.0",
 | 
				
			||||||
 | 
					    fap_category="USB",
 | 
				
			||||||
 | 
					    fap_icon="hid_usb_10px.png",
 | 
				
			||||||
 | 
					    fap_icon_assets="assets",
 | 
				
			||||||
 | 
					    fap_icon_assets_symbol="hid",
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					App(
 | 
				
			||||||
 | 
					    appid="hid_ble",
 | 
				
			||||||
 | 
					    name="Remote",
 | 
				
			||||||
 | 
					    apptype=FlipperAppType.EXTERNAL,
 | 
				
			||||||
 | 
					    entry_point="hid_ble_app",
 | 
				
			||||||
 | 
					    stack_size=1 * 1024,
 | 
				
			||||||
 | 
					    fap_description="Use Flipper as a HID remote control over Bluetooth",
 | 
				
			||||||
 | 
					    fap_version="1.0",
 | 
				
			||||||
 | 
					    fap_category="Bluetooth",
 | 
				
			||||||
 | 
					    fap_icon="hid_ble_10px.png",
 | 
				
			||||||
 | 
					    fap_icon_assets="assets",
 | 
				
			||||||
 | 
					    fap_icon_assets_symbol="hid",
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Alt_11x7.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Arr_dwn_7x9.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.5 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Arr_up_7x9.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.5 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Ble_connected_15x15.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.5 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Ble_disconnected_15x15.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 657 B  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/ButtonDown_7x4.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 102 B  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/ButtonF10_5x8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 172 B  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/ButtonF11_5x8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 173 B  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/ButtonF12_5x8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 180 B  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/ButtonF1_5x8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 177 B  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/ButtonF2_5x8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 179 B  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/ButtonF3_5x8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 178 B  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/ButtonF4_5x8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 177 B  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/ButtonF5_5x8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 178 B  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/ButtonF6_5x8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 177 B  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/ButtonF7_5x8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 176 B  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/ButtonF8_5x8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 176 B  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/ButtonF9_5x8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 179 B  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/ButtonLeft_4x7.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/ButtonRight_4x7.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.8 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/ButtonUp_7x4.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 102 B  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Button_18x18.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.5 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Circles_47x47.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Cmd_15x7.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Ctrl_15x7.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Del_12x7.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Esc_14x7.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Left_mouse_icon_9x9.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.5 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Like_def_11x9.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.5 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Like_pressed_17x17.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Ok_btn_9x9.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.5 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Ok_btn_pressed_13x13.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.5 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Pin_arrow_down_7x9.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.5 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Pin_arrow_left_9x7.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.5 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Pin_arrow_right_9x7.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.5 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Pin_arrow_up_7x9.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.5 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Pin_back_arrow_10x8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.5 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Pressed_Button_13x13.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.5 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								applications/system/hid_app/assets/Right_mouse_icon_9x9.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.5 KiB  |