feat: Replace screen capture with ScreenCaptureKit and fix performance issues #6
@ -12,7 +12,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@solidjs/router": "^0.8.4",
|
"@solidjs/router": "^0.8.4",
|
||||||
"@tauri-apps/api": "^1.6.0",
|
"@tauri-apps/api": "^2.6.0",
|
||||||
"debug": "^4.4.1",
|
"debug": "^4.4.1",
|
||||||
"solid-icons": "^1.1.0",
|
"solid-icons": "^1.1.0",
|
||||||
"solid-js": "^1.9.7",
|
"solid-js": "^1.9.7",
|
||||||
@ -21,7 +21,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "^4.1.11",
|
"@tailwindcss/postcss": "^4.1.11",
|
||||||
"@tauri-apps/cli": "^1.6.3",
|
"@tauri-apps/cli": "^2.6.2",
|
||||||
"@types/debug": "^4.1.12",
|
"@types/debug": "^4.1.12",
|
||||||
"@types/node": "^24.0.7",
|
"@types/node": "^24.0.7",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
|
120
pnpm-lock.yaml
generated
120
pnpm-lock.yaml
generated
@ -12,8 +12,8 @@ importers:
|
|||||||
specifier: ^0.8.4
|
specifier: ^0.8.4
|
||||||
version: 0.8.4(solid-js@1.9.7)
|
version: 0.8.4(solid-js@1.9.7)
|
||||||
'@tauri-apps/api':
|
'@tauri-apps/api':
|
||||||
specifier: ^1.6.0
|
specifier: ^2.6.0
|
||||||
version: 1.6.0
|
version: 2.6.0
|
||||||
debug:
|
debug:
|
||||||
specifier: ^4.4.1
|
specifier: ^4.4.1
|
||||||
version: 4.4.1
|
version: 4.4.1
|
||||||
@ -34,8 +34,8 @@ importers:
|
|||||||
specifier: ^4.1.11
|
specifier: ^4.1.11
|
||||||
version: 4.1.11
|
version: 4.1.11
|
||||||
'@tauri-apps/cli':
|
'@tauri-apps/cli':
|
||||||
specifier: ^1.6.3
|
specifier: ^2.6.2
|
||||||
version: 1.6.3
|
version: 2.6.2
|
||||||
'@types/debug':
|
'@types/debug':
|
||||||
specifier: ^4.1.12
|
specifier: ^4.1.12
|
||||||
version: 4.1.12
|
version: 4.1.12
|
||||||
@ -511,72 +511,77 @@ packages:
|
|||||||
'@tailwindcss/postcss@4.1.11':
|
'@tailwindcss/postcss@4.1.11':
|
||||||
resolution: {integrity: sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==}
|
resolution: {integrity: sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==}
|
||||||
|
|
||||||
'@tauri-apps/api@1.6.0':
|
'@tauri-apps/api@2.6.0':
|
||||||
resolution: {integrity: sha512-rqI++FWClU5I2UBp4HXFvl+sBWkdigBkxnpJDQUWttNyG7IZP4FwQGhTNL5EOw0vI8i6eSAJ5frLqO7n7jbJdg==}
|
resolution: {integrity: sha512-hRNcdercfgpzgFrMXWwNDBN0B7vNzOzRepy6ZAmhxi5mDLVPNrTpo9MGg2tN/F7JRugj4d2aF7E1rtPXAHaetg==}
|
||||||
engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'}
|
|
||||||
|
|
||||||
'@tauri-apps/cli-darwin-arm64@1.6.3':
|
'@tauri-apps/cli-darwin-arm64@2.6.2':
|
||||||
resolution: {integrity: sha512-fQN6IYSL8bG4NvkdKE4sAGF4dF/QqqQq4hOAU+t8ksOzHJr0hUlJYfncFeJYutr/MMkdF7hYKadSb0j5EE9r0A==}
|
resolution: {integrity: sha512-YlvT+Yb7u2HplyN2Cf/nBplCQARC/I4uedlYHlgtxg6rV7xbo9BvG1jLOo29IFhqA2rOp5w1LtgvVGwsOf2kxw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@tauri-apps/cli-darwin-x64@1.6.3':
|
'@tauri-apps/cli-darwin-x64@2.6.2':
|
||||||
resolution: {integrity: sha512-1yTXZzLajKAYINJOJhZfmMhCzweHSgKQ3bEgJSn6t+1vFkOgY8Yx4oFgWcybrrWI5J1ZLZAl47+LPOY81dLcyA==}
|
resolution: {integrity: sha512-21gdPWfv1bP8rkTdCL44in70QcYcPaDM70L+y78N8TkBuC+/+wqnHcwwjzb+mUyck6UoEw2DORagSI/oKKUGJw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-arm-gnueabihf@1.6.3':
|
'@tauri-apps/cli-linux-arm-gnueabihf@2.6.2':
|
||||||
resolution: {integrity: sha512-CjTEr9r9xgjcvos09AQw8QMRPuH152B1jvlZt4PfAsyJNPFigzuwed5/SF7XAd8bFikA7zArP4UT12RdBxrx7w==}
|
resolution: {integrity: sha512-MW8Y6HqHS5yzQkwGoLk/ZyE1tWpnz/seDoY4INsbvUZdknuUf80yn3H+s6eGKtT/0Bfqon/W9sY7pEkgHRPQgA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-arm64-gnu@1.6.3':
|
'@tauri-apps/cli-linux-arm64-gnu@2.6.2':
|
||||||
resolution: {integrity: sha512-G9EUUS4M8M/Jz1UKZqvJmQQCKOzgTb8/0jZKvfBuGfh5AjFBu8LHvlFpwkKVm1l4951Xg4ulUp6P9Q7WRJ9XSA==}
|
resolution: {integrity: sha512-9PdINTUtnyrnQt9hvC4y1m0NoxKSw/wUB9OTBAQabPj8WLAdvySWiUpEiqJjwLhlu4T6ltXZRpNTEzous3/RXg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-arm64-musl@1.6.3':
|
'@tauri-apps/cli-linux-arm64-musl@2.6.2':
|
||||||
resolution: {integrity: sha512-MuBTHJyNpZRbPVG8IZBN8+Zs7aKqwD22tkWVBcL1yOGL4zNNTJlkfL+zs5qxRnHlUsn6YAlbW/5HKocfpxVwBw==}
|
resolution: {integrity: sha512-LrcJTRr7FrtQlTDkYaRXIGo/8YU/xkWmBPC646WwKNZ/S6yqCiDcOMoPe7Cx4ZvcG6sK6LUCLQMfaSNEL7PT0A==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-x64-gnu@1.6.3':
|
'@tauri-apps/cli-linux-riscv64-gnu@2.6.2':
|
||||||
resolution: {integrity: sha512-Uvi7M+NK3tAjCZEY1WGel+dFlzJmqcvu3KND+nqa22762NFmOuBIZ4KJR/IQHfpEYqKFNUhJfCGnpUDfiC3Oxg==}
|
resolution: {integrity: sha512-GnTshO/BaZ9KGIazz2EiFfXGWgLur5/pjqklRA/ck42PGdUQJhV/Ao7A7TdXPjqAzpFxNo6M/Hx0GCH2iMS7IA==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@tauri-apps/cli-linux-x64-gnu@2.6.2':
|
||||||
|
resolution: {integrity: sha512-QDG3WeJD6UJekmrtVPCJRzlKgn9sGzhvD58oAw5gIU+DRovgmmG2U1jH9fS361oYGjWWO7d/KM9t0kugZzi4lQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-x64-musl@1.6.3':
|
'@tauri-apps/cli-linux-x64-musl@2.6.2':
|
||||||
resolution: {integrity: sha512-rc6B342C0ra8VezB/OJom9j/N+9oW4VRA4qMxS2f4bHY2B/z3J9NPOe6GOILeg4v/CV62ojkLsC3/K/CeF3fqQ==}
|
resolution: {integrity: sha512-TNVTDDtnWzuVqWBFdZ4+8ZTg17tc21v+CT5XBQ+KYCoYtCrIaHpW04fS5Tmudi+vYdBwoPDfwpKEB6LhCeFraQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@tauri-apps/cli-win32-arm64-msvc@1.6.3':
|
'@tauri-apps/cli-win32-arm64-msvc@2.6.2':
|
||||||
resolution: {integrity: sha512-cSH2qOBYuYC4UVIFtrc1YsGfc5tfYrotoHrpTvRjUGu0VywvmyNk82+ZsHEnWZ2UHmu3l3lXIGRqSWveLln0xg==}
|
resolution: {integrity: sha512-z77C1oa/hMLO/jM1JF39tK3M3v9nou7RsBnQoOY54z5WPcpVAbS0XdFhXB7sSN72BOiO3moDky9lQANQz6L3CA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@tauri-apps/cli-win32-ia32-msvc@1.6.3':
|
'@tauri-apps/cli-win32-ia32-msvc@2.6.2':
|
||||||
resolution: {integrity: sha512-T8V6SJQqE4PSWmYBl0ChQVmS6AR2hXFHURH2DwAhgSGSQ6uBXgwlYFcfIeQpBQA727K2Eq8X2hGfvmoySyHMRw==}
|
resolution: {integrity: sha512-TmD8BbzbjluBw8+QEIWUVmFa9aAluSkT1N937n1mpYLXcPbTpbunqRFiIznTwupoJNJIdtpF/t7BdZDRh5rrcg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [ia32]
|
cpu: [ia32]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@tauri-apps/cli-win32-x64-msvc@1.6.3':
|
'@tauri-apps/cli-win32-x64-msvc@2.6.2':
|
||||||
resolution: {integrity: sha512-HUkWZ+lYHI/Gjkh2QjHD/OBDpqLVmvjZGpLK9losur1Eg974Jip6k+vsoTUxQBCBDfj30eDBct9E1FvXOspWeg==}
|
resolution: {integrity: sha512-ItB8RCKk+nCmqOxOvbNtltz6x1A4QX6cSM21kj3NkpcnjT9rHSMcfyf8WVI2fkoMUJR80iqCblUX6ARxC3lj6w==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@tauri-apps/cli@1.6.3':
|
'@tauri-apps/cli@2.6.2':
|
||||||
resolution: {integrity: sha512-q46umd6QLRKDd4Gg6WyZBGa2fWvk0pbeUA5vFomm4uOs1/17LIciHv2iQ4UD+2Yv5H7AO8YiE1t50V0POiEGEw==}
|
resolution: {integrity: sha512-s1/eyBHxk0wG1blLeOY2IDjgZcxVrkxU5HFL8rNDwjYGr0o7yr3RAtwmuUPhz13NO+xGAL1bJZaLFBdp+5joKg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@ -851,11 +856,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
semver@7.7.2:
|
|
||||||
resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
seroval-plugins@1.3.2:
|
seroval-plugins@1.3.2:
|
||||||
resolution: {integrity: sha512-0QvCV2lM3aj/U3YozDiVwx9zpH0q8A60CTWIv4Jszj/givcudPb48B+rkU5D51NJ0pTpweGMttHjboPa9/zoIQ==}
|
resolution: {integrity: sha512-0QvCV2lM3aj/U3YozDiVwx9zpH0q8A60CTWIv4Jszj/givcudPb48B+rkU5D51NJ0pTpweGMttHjboPa9/zoIQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@ -1344,52 +1344,54 @@ snapshots:
|
|||||||
postcss: 8.5.6
|
postcss: 8.5.6
|
||||||
tailwindcss: 4.1.11
|
tailwindcss: 4.1.11
|
||||||
|
|
||||||
'@tauri-apps/api@1.6.0': {}
|
'@tauri-apps/api@2.6.0': {}
|
||||||
|
|
||||||
'@tauri-apps/cli-darwin-arm64@1.6.3':
|
'@tauri-apps/cli-darwin-arm64@2.6.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tauri-apps/cli-darwin-x64@1.6.3':
|
'@tauri-apps/cli-darwin-x64@2.6.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-arm-gnueabihf@1.6.3':
|
'@tauri-apps/cli-linux-arm-gnueabihf@2.6.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-arm64-gnu@1.6.3':
|
'@tauri-apps/cli-linux-arm64-gnu@2.6.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-arm64-musl@1.6.3':
|
'@tauri-apps/cli-linux-arm64-musl@2.6.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-x64-gnu@1.6.3':
|
'@tauri-apps/cli-linux-riscv64-gnu@2.6.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tauri-apps/cli-linux-x64-musl@1.6.3':
|
'@tauri-apps/cli-linux-x64-gnu@2.6.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tauri-apps/cli-win32-arm64-msvc@1.6.3':
|
'@tauri-apps/cli-linux-x64-musl@2.6.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tauri-apps/cli-win32-ia32-msvc@1.6.3':
|
'@tauri-apps/cli-win32-arm64-msvc@2.6.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tauri-apps/cli-win32-x64-msvc@1.6.3':
|
'@tauri-apps/cli-win32-ia32-msvc@2.6.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tauri-apps/cli@1.6.3':
|
'@tauri-apps/cli-win32-x64-msvc@2.6.2':
|
||||||
dependencies:
|
optional: true
|
||||||
semver: 7.7.2
|
|
||||||
|
'@tauri-apps/cli@2.6.2':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@tauri-apps/cli-darwin-arm64': 1.6.3
|
'@tauri-apps/cli-darwin-arm64': 2.6.2
|
||||||
'@tauri-apps/cli-darwin-x64': 1.6.3
|
'@tauri-apps/cli-darwin-x64': 2.6.2
|
||||||
'@tauri-apps/cli-linux-arm-gnueabihf': 1.6.3
|
'@tauri-apps/cli-linux-arm-gnueabihf': 2.6.2
|
||||||
'@tauri-apps/cli-linux-arm64-gnu': 1.6.3
|
'@tauri-apps/cli-linux-arm64-gnu': 2.6.2
|
||||||
'@tauri-apps/cli-linux-arm64-musl': 1.6.3
|
'@tauri-apps/cli-linux-arm64-musl': 2.6.2
|
||||||
'@tauri-apps/cli-linux-x64-gnu': 1.6.3
|
'@tauri-apps/cli-linux-riscv64-gnu': 2.6.2
|
||||||
'@tauri-apps/cli-linux-x64-musl': 1.6.3
|
'@tauri-apps/cli-linux-x64-gnu': 2.6.2
|
||||||
'@tauri-apps/cli-win32-arm64-msvc': 1.6.3
|
'@tauri-apps/cli-linux-x64-musl': 2.6.2
|
||||||
'@tauri-apps/cli-win32-ia32-msvc': 1.6.3
|
'@tauri-apps/cli-win32-arm64-msvc': 2.6.2
|
||||||
'@tauri-apps/cli-win32-x64-msvc': 1.6.3
|
'@tauri-apps/cli-win32-ia32-msvc': 2.6.2
|
||||||
|
'@tauri-apps/cli-win32-x64-msvc': 2.6.2
|
||||||
|
|
||||||
'@types/babel__core@7.20.5':
|
'@types/babel__core@7.20.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -1653,8 +1655,6 @@ snapshots:
|
|||||||
|
|
||||||
semver@6.3.1: {}
|
semver@6.3.1: {}
|
||||||
|
|
||||||
semver@7.7.2: {}
|
|
||||||
|
|
||||||
seroval-plugins@1.3.2(seroval@1.3.2):
|
seroval-plugins@1.3.2(seroval@1.3.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
seroval: 1.3.2
|
seroval: 1.3.2
|
||||||
|
2199
src-tauri/Cargo.lock
generated
2199
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -10,11 +10,14 @@ edition = "2021"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "1.2", features = [] }
|
tauri-build = { version = "2.0", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tauri = { version = "1.2", features = [ "protocol-all", "shell-open"] }
|
tauri = { version = "2.0", features = ["tray-icon"] }
|
||||||
|
tauri-plugin-shell = "2.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
dirs = "5.0"
|
||||||
|
regex = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
core-graphics = "0.22.3"
|
core-graphics = "0.22.3"
|
||||||
display-info = "0.4.1"
|
display-info = "0.4.1"
|
||||||
@ -38,6 +41,7 @@ futures = "0.3.28"
|
|||||||
ddc-hi = "0.4.1"
|
ddc-hi = "0.4.1"
|
||||||
coreaudio-rs = "0.11.2"
|
coreaudio-rs = "0.11.2"
|
||||||
screen-capture-kit = "0.3.1"
|
screen-capture-kit = "0.3.1"
|
||||||
|
image = { version = "0.24", features = ["jpeg"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# this feature is used for production builds or when `devPath` points to the filesystem
|
# this feature is used for production builds or when `devPath` points to the filesystem
|
||||||
|
10
src-tauri/capabilities/default.json
Normal file
10
src-tauri/capabilities/default.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
|
"identifier": "default",
|
||||||
|
"description": "Capability for the main application window",
|
||||||
|
"windows": ["main"],
|
||||||
|
"permissions": [
|
||||||
|
"core:default",
|
||||||
|
"shell:allow-open"
|
||||||
|
]
|
||||||
|
}
|
1
src-tauri/gen/schemas/acl-manifests.json
Normal file
1
src-tauri/gen/schemas/acl-manifests.json
Normal file
File diff suppressed because one or more lines are too long
1
src-tauri/gen/schemas/capabilities.json
Normal file
1
src-tauri/gen/schemas/capabilities.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"default":{"identifier":"default","description":"Capability for the main application window","local":true,"windows":["main"],"permissions":["core:default","shell:allow-open"]}}
|
2504
src-tauri/gen/schemas/desktop-schema.json
Normal file
2504
src-tauri/gen/schemas/desktop-schema.json
Normal file
File diff suppressed because it is too large
Load Diff
2504
src-tauri/gen/schemas/macOS-schema.json
Normal file
2504
src-tauri/gen/schemas/macOS-schema.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -3,7 +3,6 @@ use std::env::current_dir;
|
|||||||
use display_info::DisplayInfo;
|
use display_info::DisplayInfo;
|
||||||
use paris::{error, info};
|
use paris::{error, info};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tauri::api::path::config_dir;
|
|
||||||
|
|
||||||
use crate::screenshot::LedSamplePoints;
|
use crate::screenshot::LedSamplePoints;
|
||||||
|
|
||||||
@ -55,7 +54,7 @@ impl LedStripConfigGroup {
|
|||||||
let displays = DisplayInfo::all()?;
|
let displays = DisplayInfo::all()?;
|
||||||
|
|
||||||
// config path
|
// config path
|
||||||
let path = config_dir()
|
let path = dirs::config_dir()
|
||||||
.unwrap_or(current_dir().unwrap())
|
.unwrap_or(current_dir().unwrap())
|
||||||
.join(CONFIG_FILE_NAME);
|
.join(CONFIG_FILE_NAME);
|
||||||
|
|
||||||
@ -83,7 +82,7 @@ impl LedStripConfigGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn write_config(configs: &Self) -> anyhow::Result<()> {
|
pub async fn write_config(configs: &Self) -> anyhow::Result<()> {
|
||||||
let path = config_dir()
|
let path = dirs::config_dir()
|
||||||
.unwrap_or(current_dir().unwrap())
|
.unwrap_or(current_dir().unwrap())
|
||||||
.join(CONFIG_FILE_NAME);
|
.join(CONFIG_FILE_NAME);
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ use std::{env::current_dir, sync::Arc, time::Duration};
|
|||||||
|
|
||||||
use ddc_hi::Display;
|
use ddc_hi::Display;
|
||||||
use paris::{error, info, warn};
|
use paris::{error, info, warn};
|
||||||
use tauri::api::path::config_dir;
|
use dirs::config_dir;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
sync::{broadcast, watch, OnceCell, RwLock},
|
sync::{broadcast, watch, OnceCell, RwLock},
|
||||||
task::yield_now,
|
task::yield_now,
|
||||||
|
@ -18,7 +18,9 @@ use screenshot::Screenshot;
|
|||||||
use screenshot_manager::ScreenshotManager;
|
use screenshot_manager::ScreenshotManager;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::to_string;
|
use serde_json::to_string;
|
||||||
use tauri::{http::ResponseBuilder, regex, Manager};
|
use tauri::{Manager, Emitter, Runtime};
|
||||||
|
use regex;
|
||||||
|
use tauri::http::{Request, Response};
|
||||||
use volume::VolumeManager;
|
use volume::VolumeManager;
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
#[serde(remote = "DisplayInfo")]
|
#[serde(remote = "DisplayInfo")]
|
||||||
@ -212,6 +214,123 @@ async fn get_displays() -> Vec<DisplayState> {
|
|||||||
display_manager.get_displays().await
|
display_manager.get_displays().await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Protocol handler for ambient-light://
|
||||||
|
fn handle_ambient_light_protocol<R: Runtime>(
|
||||||
|
_ctx: tauri::UriSchemeContext<R>,
|
||||||
|
request: Request<Vec<u8>>
|
||||||
|
) -> Response<Vec<u8>> {
|
||||||
|
let url = request.uri();
|
||||||
|
// info!("Handling ambient-light protocol request: {}", url);
|
||||||
|
|
||||||
|
// Parse the URL to extract parameters
|
||||||
|
let url_str = url.to_string();
|
||||||
|
let re = regex::Regex::new(r"ambient-light://displays/(\d+)\?width=(\d+)&height=(\d+)").unwrap();
|
||||||
|
|
||||||
|
if let Some(captures) = re.captures(&url_str) {
|
||||||
|
let display_id: u32 = captures[1].parse().unwrap_or(0);
|
||||||
|
let width: u32 = captures[2].parse().unwrap_or(400);
|
||||||
|
let height: u32 = captures[3].parse().unwrap_or(300);
|
||||||
|
|
||||||
|
// info!("Efficient screenshot request for display {}, {}x{}", display_id, width, height);
|
||||||
|
|
||||||
|
// Optimized screenshot processing with much smaller intermediate size
|
||||||
|
// info!("Screenshot request received: display_id={}, width={}, height={}", display_id, width, height);
|
||||||
|
|
||||||
|
let screenshot_data = tokio::task::block_in_place(|| {
|
||||||
|
tokio::runtime::Handle::current().block_on(async {
|
||||||
|
let screenshot_manager = ScreenshotManager::global().await;
|
||||||
|
let channels = screenshot_manager.channels.read().await;
|
||||||
|
|
||||||
|
if let Some(rx) = channels.get(&display_id) {
|
||||||
|
let rx = rx.read().await;
|
||||||
|
let screenshot = rx.borrow().clone();
|
||||||
|
let bytes = screenshot.bytes.read().await.to_owned();
|
||||||
|
|
||||||
|
// Use much smaller intermediate resolution for performance
|
||||||
|
let intermediate_width = 800; // Much smaller than original 5120
|
||||||
|
let intermediate_height = 450; // Much smaller than original 2880
|
||||||
|
|
||||||
|
// Convert BGRA to RGBA format
|
||||||
|
let mut rgba_bytes = bytes.as_ref().clone();
|
||||||
|
for chunk in rgba_bytes.chunks_exact_mut(4) {
|
||||||
|
chunk.swap(0, 2); // Swap B and R channels
|
||||||
|
}
|
||||||
|
|
||||||
|
let image_result = image::RgbaImage::from_raw(
|
||||||
|
screenshot.width as u32,
|
||||||
|
screenshot.height as u32,
|
||||||
|
rgba_bytes,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(img) = image_result {
|
||||||
|
// Step 1: Fast downscale to intermediate size
|
||||||
|
let intermediate_image = image::imageops::resize(
|
||||||
|
&img,
|
||||||
|
intermediate_width,
|
||||||
|
intermediate_height,
|
||||||
|
image::imageops::FilterType::Nearest, // Fastest possible
|
||||||
|
);
|
||||||
|
|
||||||
|
// Step 2: Scale to final target size
|
||||||
|
let final_image = if width == intermediate_width && height == intermediate_height {
|
||||||
|
intermediate_image
|
||||||
|
} else {
|
||||||
|
image::imageops::resize(
|
||||||
|
&intermediate_image,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
image::imageops::FilterType::Triangle,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let raw_data = final_image.into_raw();
|
||||||
|
// info!("Efficient resize completed: {}x{}, {} bytes", width, height, raw_data.len());
|
||||||
|
Ok(raw_data)
|
||||||
|
} else {
|
||||||
|
error!("Failed to create image from raw bytes");
|
||||||
|
Err("Failed to create image from raw bytes".to_string())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!("Display {} not found", display_id);
|
||||||
|
Err(format!("Display {} not found", display_id))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
match screenshot_data {
|
||||||
|
Ok(data) => {
|
||||||
|
Response::builder()
|
||||||
|
.header("Content-Type", "application/octet-stream")
|
||||||
|
.header("Access-Control-Allow-Origin", "*")
|
||||||
|
.header("X-Image-Width", width.to_string())
|
||||||
|
.header("X-Image-Height", height.to_string())
|
||||||
|
.body(data)
|
||||||
|
.unwrap_or_else(|_| {
|
||||||
|
Response::builder()
|
||||||
|
.status(500)
|
||||||
|
.body("Failed to build response".as_bytes().to_vec())
|
||||||
|
.unwrap()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to get screenshot: {}", e);
|
||||||
|
Response::builder()
|
||||||
|
.status(500)
|
||||||
|
.body(format!("Error: {}", e).into_bytes())
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("Invalid ambient-light URL format: {}", url_str);
|
||||||
|
Response::builder()
|
||||||
|
.status(400)
|
||||||
|
.body("Invalid URL format".as_bytes().to_vec())
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
@ -231,6 +350,7 @@ async fn main() {
|
|||||||
let _volume = VolumeManager::global().await;
|
let _volume = VolumeManager::global().await;
|
||||||
|
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_shell::init())
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
greet,
|
greet,
|
||||||
list_display_info,
|
list_display_info,
|
||||||
@ -247,145 +367,9 @@ async fn main() {
|
|||||||
get_boards,
|
get_boards,
|
||||||
get_displays
|
get_displays
|
||||||
])
|
])
|
||||||
.register_uri_scheme_protocol("ambient-light", move |_app, request| {
|
.register_uri_scheme_protocol("ambient-light", handle_ambient_light_protocol)
|
||||||
let response = ResponseBuilder::new().header("Access-Control-Allow-Origin", "*");
|
|
||||||
|
|
||||||
let uri = request.uri();
|
|
||||||
let uri = percent_encoding::percent_decode_str(uri)
|
|
||||||
.decode_utf8()
|
|
||||||
.unwrap()
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let url = url_build_parse::parse_url(uri.as_str());
|
|
||||||
|
|
||||||
if let Err(err) = url {
|
|
||||||
error!("url parse error: {}", err);
|
|
||||||
return response
|
|
||||||
.status(500)
|
|
||||||
.mimetype("text/plain")
|
|
||||||
.body("Parse uri failed.".as_bytes().to_vec());
|
|
||||||
}
|
|
||||||
|
|
||||||
let url = url.unwrap();
|
|
||||||
|
|
||||||
let re = regex::Regex::new(r"^/(\d+)$").unwrap();
|
|
||||||
let path = url.path;
|
|
||||||
let captures = re.captures(path.as_str());
|
|
||||||
|
|
||||||
if let None = captures {
|
|
||||||
error!("path not matched: {:?}", path);
|
|
||||||
return response
|
|
||||||
.status(404)
|
|
||||||
.mimetype("text/plain")
|
|
||||||
.body("Path Not Found.".as_bytes().to_vec());
|
|
||||||
}
|
|
||||||
|
|
||||||
let captures = captures.unwrap();
|
|
||||||
|
|
||||||
let display_id = captures[1].parse::<u32>().unwrap();
|
|
||||||
|
|
||||||
let bytes = tokio::task::block_in_place(move || {
|
|
||||||
tauri::async_runtime::block_on(async move {
|
|
||||||
|
|
||||||
let screenshot_manager = ScreenshotManager::global().await;
|
|
||||||
let rx: Result<tokio::sync::watch::Receiver<Screenshot>, anyhow::Error> =
|
|
||||||
screenshot_manager.subscribe_by_display_id(display_id).await;
|
|
||||||
|
|
||||||
if let Err(err) = rx {
|
|
||||||
anyhow::bail!("Display#{}: not found. {}", display_id, err);
|
|
||||||
}
|
|
||||||
let mut rx = rx.unwrap();
|
|
||||||
|
|
||||||
if rx.changed().await.is_err() {
|
|
||||||
anyhow::bail!("Display#{}: no more screenshot.", display_id);
|
|
||||||
}
|
|
||||||
let screenshot = rx.borrow().clone();
|
|
||||||
let bytes = screenshot.bytes.read().await;
|
|
||||||
if bytes.len() == 0 {
|
|
||||||
anyhow::bail!("Display#{}: no screenshot.", display_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let (scale_factor_x, scale_factor_y, width, height) = if url.query.is_some()
|
|
||||||
&& url.query.as_ref().unwrap().contains_key("height")
|
|
||||||
&& url.query.as_ref().unwrap().contains_key("width")
|
|
||||||
{
|
|
||||||
let width = url.query.as_ref().unwrap()["width"]
|
|
||||||
.parse::<u32>()
|
|
||||||
.map_err(|err| {
|
|
||||||
warn!("width parse error: {}", err);
|
|
||||||
err
|
|
||||||
})?;
|
|
||||||
let height = url.query.as_ref().unwrap()["height"]
|
|
||||||
.parse::<u32>()
|
|
||||||
.map_err(|err| {
|
|
||||||
warn!("height parse error: {}", err);
|
|
||||||
err
|
|
||||||
})?;
|
|
||||||
(
|
|
||||||
screenshot.width as f32 / width as f32,
|
|
||||||
screenshot.height as f32 / height as f32,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
log::debug!("scale by scale_factor");
|
|
||||||
let scale_factor = screenshot.scale_factor;
|
|
||||||
(
|
|
||||||
scale_factor,
|
|
||||||
scale_factor,
|
|
||||||
(screenshot.width as f32 / scale_factor) as u32,
|
|
||||||
(screenshot.height as f32 / scale_factor) as u32,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
log::debug!(
|
|
||||||
"scale by query. width: {}, height: {}, scale_factor: {}, len: {}",
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
screenshot.width as f32 / width as f32,
|
|
||||||
width * height * 4,
|
|
||||||
);
|
|
||||||
|
|
||||||
let bytes_per_row = screenshot.bytes_per_row as f32;
|
|
||||||
|
|
||||||
let mut rgba_buffer = vec![0u8; (width * height * 4) as usize];
|
|
||||||
|
|
||||||
for y in 0..height {
|
|
||||||
for x in 0..width {
|
|
||||||
let offset = ((y as f32) * scale_factor_y).floor() as usize
|
|
||||||
* bytes_per_row as usize
|
|
||||||
+ ((x as f32) * scale_factor_x).floor() as usize * 4;
|
|
||||||
let b = bytes[offset];
|
|
||||||
let g = bytes[offset + 1];
|
|
||||||
let r = bytes[offset + 2];
|
|
||||||
let a = bytes[offset + 3];
|
|
||||||
let offset_2 = (y * width + x) as usize * 4;
|
|
||||||
rgba_buffer[offset_2] = r;
|
|
||||||
rgba_buffer[offset_2 + 1] = g;
|
|
||||||
rgba_buffer[offset_2 + 2] = b;
|
|
||||||
rgba_buffer[offset_2 + 3] = a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Ok(rgba_buffer.clone())
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Ok(bytes) = bytes {
|
|
||||||
return response
|
|
||||||
.mimetype("octet/stream")
|
|
||||||
.status(200)
|
|
||||||
.body(bytes.to_vec());
|
|
||||||
}
|
|
||||||
let err = bytes.unwrap_err();
|
|
||||||
error!("request screenshot bin data failed: {}", err);
|
|
||||||
return response
|
|
||||||
.mimetype("text/plain")
|
|
||||||
.status(500)
|
|
||||||
.body(err.to_string().into_bytes());
|
|
||||||
})
|
|
||||||
.setup(move |app| {
|
.setup(move |app| {
|
||||||
let app_handle = app.handle().clone();
|
let app_handle = app.handle().clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
@ -401,7 +385,7 @@ async fn main() {
|
|||||||
|
|
||||||
let config = config_update_receiver.borrow().clone();
|
let config = config_update_receiver.borrow().clone();
|
||||||
|
|
||||||
app_handle.emit_all("config_changed", config).unwrap();
|
app_handle.emit("config_changed", config).unwrap();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -418,7 +402,7 @@ async fn main() {
|
|||||||
let publisher = publisher_update_receiver.borrow().clone();
|
let publisher = publisher_update_receiver.borrow().clone();
|
||||||
|
|
||||||
app_handle
|
app_handle
|
||||||
.emit_all("led_sorted_colors_changed", publisher)
|
.emit("led_sorted_colors_changed", publisher)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -436,7 +420,7 @@ async fn main() {
|
|||||||
let publisher = publisher_update_receiver.borrow().clone();
|
let publisher = publisher_update_receiver.borrow().clone();
|
||||||
|
|
||||||
app_handle
|
app_handle
|
||||||
.emit_all("led_colors_changed", publisher)
|
.emit("led_colors_changed", publisher)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -457,7 +441,7 @@ async fn main() {
|
|||||||
|
|
||||||
let boards = boards.into_iter().collect::<Vec<_>>();
|
let boards = boards.into_iter().collect::<Vec<_>>();
|
||||||
|
|
||||||
app_handle.emit_all("boards_changed", boards).unwrap();
|
app_handle.emit("boards_changed", boards).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@ -478,7 +462,7 @@ async fn main() {
|
|||||||
|
|
||||||
log::info!("displays changed. emit displays_changed event.");
|
log::info!("displays changed. emit displays_changed event.");
|
||||||
|
|
||||||
app_handle.emit_all("displays_changed", displays).unwrap();
|
app_handle.emit("displays_changed", displays).unwrap();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -145,9 +145,16 @@ impl Screenshot {
|
|||||||
for (x, y) in led_points {
|
for (x, y) in led_points {
|
||||||
// log::info!("x: {}, y: {}, bytes_per_row: {}", x, y, bytes_per_row);
|
// log::info!("x: {}, y: {}, bytes_per_row: {}", x, y, bytes_per_row);
|
||||||
let position = x * 4 + y * bytes_per_row;
|
let position = x * 4 + y * bytes_per_row;
|
||||||
b += bitmap[position] as f64;
|
|
||||||
g += bitmap[position + 1] as f64;
|
// Add bounds checking to prevent index out of bounds
|
||||||
r += bitmap[position + 2] as f64;
|
if position + 2 < bitmap.len() {
|
||||||
|
b += bitmap[position] as f64;
|
||||||
|
g += bitmap[position + 1] as f64;
|
||||||
|
r += bitmap[position + 2] as f64;
|
||||||
|
} else {
|
||||||
|
// Skip invalid positions or use default values
|
||||||
|
log::warn!("Invalid pixel position: x={}, y={}, position={}, bitmap_len={}", x, y, position, bitmap.len());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let color = LedColor::new((r / len) as u8, (g / len) as u8, (b / len) as u8);
|
let color = LedColor::new((r / len) as u8, (g / len) as u8, (b / len) as u8);
|
||||||
colors.push(color);
|
colors.push(color);
|
||||||
@ -169,9 +176,16 @@ impl Screenshot {
|
|||||||
for (x, y) in led_points {
|
for (x, y) in led_points {
|
||||||
// log::info!("x: {}, y: {}, bytes_per_row: {}", x, y, bytes_per_row);
|
// log::info!("x: {}, y: {}, bytes_per_row: {}", x, y, bytes_per_row);
|
||||||
let position = x * 4 + y * bytes_per_row;
|
let position = x * 4 + y * bytes_per_row;
|
||||||
b += bitmap[position] as f64;
|
|
||||||
g += bitmap[position + 1] as f64;
|
// Add bounds checking to prevent index out of bounds
|
||||||
r += bitmap[position + 2] as f64;
|
if position + 2 < bitmap.len() as usize {
|
||||||
|
b += bitmap[position] as f64;
|
||||||
|
g += bitmap[position + 1] as f64;
|
||||||
|
r += bitmap[position + 2] as f64;
|
||||||
|
} else {
|
||||||
|
// Skip invalid positions or use default values
|
||||||
|
log::warn!("Invalid pixel position in CG image: x={}, y={}, position={}, bitmap_len={}", x, y, position, bitmap.len());
|
||||||
|
}
|
||||||
// log::info!("position: {}, total: {}", position, bitmap.len());
|
// log::info!("position: {}, total: {}", position, bitmap.len());
|
||||||
}
|
}
|
||||||
let color = LedColor::new((r / len) as u8, (g / len) as u8, (b / len) as u8);
|
let color = LedColor::new((r / len) as u8, (g / len) as u8, (b / len) as u8);
|
||||||
|
@ -1,51 +1,24 @@
|
|||||||
{
|
{
|
||||||
|
"productName": "test-demo",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"identifier": "cc.ivanli.ambient-light.desktop",
|
||||||
|
"mainBinaryName": "test-demo",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "pnpm dev",
|
"beforeDevCommand": "pnpm dev",
|
||||||
"beforeBuildCommand": "pnpm build",
|
"beforeBuildCommand": "pnpm build",
|
||||||
"devPath": "http://localhost:1420",
|
"devUrl": "http://localhost:1420",
|
||||||
"distDir": "../dist",
|
"frontendDist": "../dist"
|
||||||
"withGlobalTauri": false
|
|
||||||
},
|
},
|
||||||
"package": {
|
"app": {
|
||||||
"productName": "test-demo",
|
"withGlobalTauri": false,
|
||||||
"version": "0.0.1"
|
"security": {
|
||||||
},
|
"csp": null,
|
||||||
"tauri": {
|
"assetProtocol": {
|
||||||
"allowlist": {
|
"scope": [
|
||||||
"all": false,
|
|
||||||
"shell": {
|
|
||||||
"all": false,
|
|
||||||
"open": true
|
|
||||||
},
|
|
||||||
"protocol": {
|
|
||||||
"all": true,
|
|
||||||
"asset": true,
|
|
||||||
"assetScope": [
|
|
||||||
"**"
|
"**"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bundle": {
|
|
||||||
"active": true,
|
|
||||||
"icon": [
|
|
||||||
"icons/32x32.png",
|
|
||||||
"icons/128x128.png",
|
|
||||||
"icons/128x128@2x.png",
|
|
||||||
"icons/icon.icns",
|
|
||||||
"icons/icon.ico"
|
|
||||||
],
|
|
||||||
"identifier": "cc.ivanli.ambient-light.desktop",
|
|
||||||
"targets": "all",
|
|
||||||
"macOS": {
|
|
||||||
"minimumSystemVersion": "13"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"security": {
|
|
||||||
"csp": null
|
|
||||||
},
|
|
||||||
"updater": {
|
|
||||||
"active": false
|
|
||||||
},
|
|
||||||
"windows": [
|
"windows": [
|
||||||
{
|
{
|
||||||
"fullscreen": false,
|
"fullscreen": false,
|
||||||
@ -55,5 +28,19 @@
|
|||||||
"height": 600
|
"height": 600
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"active": true,
|
||||||
|
"icon": [
|
||||||
|
"icons/32x32.png",
|
||||||
|
"icons/128x128.png",
|
||||||
|
"icons/128x128@2x.png",
|
||||||
|
"icons/icon.icns",
|
||||||
|
"icons/icon.ico"
|
||||||
|
],
|
||||||
|
"targets": "all",
|
||||||
|
"macOS": {
|
||||||
|
"minimumSystemVersion": "13"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { Routes, Route } from '@solidjs/router';
|
|||||||
import { LedStripConfiguration } from './components/led-strip-configuration/led-strip-configuration';
|
import { LedStripConfiguration } from './components/led-strip-configuration/led-strip-configuration';
|
||||||
import { WhiteBalance } from './components/white-balance/white-balance';
|
import { WhiteBalance } from './components/white-balance/white-balance';
|
||||||
import { createEffect } from 'solid-js';
|
import { createEffect } from 'solid-js';
|
||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { setLedStripStore } from './stores/led-strip.store';
|
import { setLedStripStore } from './stores/led-strip.store';
|
||||||
import { LedStripConfigContainer } from './models/led-strip-config';
|
import { LedStripConfigContainer } from './models/led-strip-config';
|
||||||
import { InfoIndex } from './components/info/info-index';
|
import { InfoIndex } from './components/info/info-index';
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Component, For, createEffect, createSignal } from 'solid-js';
|
import { Component, For, createEffect, createSignal } from 'solid-js';
|
||||||
import { listen } from '@tauri-apps/api/event';
|
import { listen } from '@tauri-apps/api/event';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { DisplayState, RawDisplayState } from '../../models/display-state.model';
|
import { DisplayState, RawDisplayState } from '../../models/display-state.model';
|
||||||
import { DisplayStateCard } from './display-state-card';
|
import { DisplayStateCard } from './display-state-card';
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import { Component, For, createEffect, createSignal } from 'solid-js';
|
|||||||
import { BoardInfo } from '../../models/board-info.model';
|
import { BoardInfo } from '../../models/board-info.model';
|
||||||
import { listen } from '@tauri-apps/api/event';
|
import { listen } from '@tauri-apps/api/event';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { BoardInfoPanel } from './board-info-panel';
|
import { BoardInfoPanel } from './board-info-panel';
|
||||||
|
|
||||||
const logger = debug('app:components:info:board-index');
|
const logger = debug('app:components:info:board-index');
|
||||||
|
@ -23,7 +23,6 @@ export const DisplayView: Component<DisplayViewProps> = (props) => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const ledStripConfigs = createMemo(() => {
|
const ledStripConfigs = createMemo(() => {
|
||||||
console.log('ledStripConfigs', ledStripStore.strips);
|
|
||||||
return ledStripStore.strips.filter((c) => c.display_id === props.display.id);
|
return ledStripStore.strips.filter((c) => c.display_id === props.display.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { createEffect, onCleanup } from 'solid-js';
|
import { createEffect, onCleanup } from 'solid-js';
|
||||||
import { invoke } from '@tauri-apps/api/tauri';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { DisplayView } from './display-view';
|
import { DisplayView } from './display-view';
|
||||||
import { DisplayListContainer } from './display-list-container';
|
import { DisplayListContainer } from './display-list-container';
|
||||||
import { displayStore, setDisplayStore } from '../../stores/display.store';
|
import { displayStore, setDisplayStore } from '../../stores/display.store';
|
||||||
@ -22,7 +22,6 @@ export const LedStripConfiguration = () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
invoke<LedStripConfigContainer>('read_led_strip_configs').then((configs) => {
|
invoke<LedStripConfigContainer>('read_led_strip_configs').then((configs) => {
|
||||||
console.log(configs);
|
|
||||||
setLedStripStore(configs);
|
setLedStripStore(configs);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -31,7 +30,6 @@ export const LedStripConfiguration = () => {
|
|||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const unlisten = listen('config_changed', (event) => {
|
const unlisten = listen('config_changed', (event) => {
|
||||||
const { strips, mappers } = event.payload as LedStripConfigContainer;
|
const { strips, mappers } = event.payload as LedStripConfigContainer;
|
||||||
console.log(event.payload);
|
|
||||||
setLedStripStore({
|
setLedStripStore({
|
||||||
strips,
|
strips,
|
||||||
mappers,
|
mappers,
|
||||||
@ -46,17 +44,11 @@ export const LedStripConfiguration = () => {
|
|||||||
// listen to led_colors_changed event
|
// listen to led_colors_changed event
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const unlisten = listen<Uint8ClampedArray>('led_colors_changed', (event) => {
|
const unlisten = listen<Uint8ClampedArray>('led_colors_changed', (event) => {
|
||||||
console.log('Received led_colors_changed event:', {
|
|
||||||
hidden: window.document.hidden,
|
|
||||||
colorsLength: event.payload.length,
|
|
||||||
firstFewColors: Array.from(event.payload.slice(0, 12))
|
|
||||||
});
|
|
||||||
if (!window.document.hidden) {
|
if (!window.document.hidden) {
|
||||||
const colors = event.payload;
|
const colors = event.payload;
|
||||||
setLedStripStore({
|
setLedStripStore({
|
||||||
colors,
|
colors,
|
||||||
});
|
});
|
||||||
console.log('Updated ledStripStore.colors with length:', colors.length);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -68,17 +60,11 @@ export const LedStripConfiguration = () => {
|
|||||||
// listen to led_sorted_colors_changed event
|
// listen to led_sorted_colors_changed event
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const unlisten = listen<Uint8ClampedArray>('led_sorted_colors_changed', (event) => {
|
const unlisten = listen<Uint8ClampedArray>('led_sorted_colors_changed', (event) => {
|
||||||
console.log('Received led_sorted_colors_changed event:', {
|
|
||||||
hidden: window.document.hidden,
|
|
||||||
sortedColorsLength: event.payload.length,
|
|
||||||
firstFewSortedColors: Array.from(event.payload.slice(0, 12))
|
|
||||||
});
|
|
||||||
if (!window.document.hidden) {
|
if (!window.document.hidden) {
|
||||||
const sortedColors = event.payload;
|
const sortedColors = event.payload;
|
||||||
setLedStripStore({
|
setLedStripStore({
|
||||||
sortedColors,
|
sortedColors,
|
||||||
});
|
});
|
||||||
console.log('Updated ledStripStore.sortedColors with length:', sortedColors.length);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
createEffect,
|
createEffect,
|
||||||
@ -34,7 +34,7 @@ export const Pixel: Component<PixelProps> = (props) => {
|
|||||||
title={props.color}
|
title={props.color}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="absolute top-1/2 -translate-y-1/2 h-2.5 w-2.5 rounded-full ring-1 ring-stone-300"
|
class="absolute top-1/2 -translate-y-1/2 h-2 w-2 rounded-full ring-1 ring-stone-300/30"
|
||||||
style={style()}
|
style={style()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -60,27 +60,46 @@ export const LedStripPart: Component<LedStripPartProps> = (props) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
console.log(`LED strip not found for display ${localProps.config.display_id}, border ${localProps.config.border}`);
|
console.log('🔍 LED: Strip config not found', {
|
||||||
|
displayId: localProps.config.display_id,
|
||||||
|
border: localProps.config.border,
|
||||||
|
availableStrips: ledStripStore.strips.length
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapper = ledStripStore.mappers[index];
|
const mapper = ledStripStore.mappers[index];
|
||||||
if (!mapper) {
|
if (!mapper) {
|
||||||
console.log(`Mapper not found for index ${index}`);
|
console.log('🔍 LED: Mapper not found', { index, mappersCount: ledStripStore.mappers.length });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const offset = mapper.pos * 3;
|
const offset = mapper.start * 3;
|
||||||
console.log(`Updating LED strip colors for ${localProps.config.border}, offset: ${offset}, colors length: ${ledStripStore.colors.length}`);
|
|
||||||
|
console.log('🎨 LED: Updating colors', {
|
||||||
|
displayId: localProps.config.display_id,
|
||||||
|
border: localProps.config.border,
|
||||||
|
stripLength: localProps.config.len,
|
||||||
|
mapperPos: mapper.pos,
|
||||||
|
offset,
|
||||||
|
colorsArrayLength: ledStripStore.colors.length,
|
||||||
|
firstFewColors: Array.from(ledStripStore.colors.slice(offset, offset + 9))
|
||||||
|
});
|
||||||
|
|
||||||
const colors = new Array(localProps.config.len).fill(null).map((_, i) => {
|
const colors = new Array(localProps.config.len).fill(null).map((_, i) => {
|
||||||
const index = offset + i * 3;
|
const index = offset + i * 3;
|
||||||
return `rgb(${ledStripStore.colors[index]}, ${ledStripStore.colors[index + 1]}, ${
|
const r = ledStripStore.colors[index] || 0;
|
||||||
ledStripStore.colors[index + 2]
|
const g = ledStripStore.colors[index + 1] || 0;
|
||||||
})`;
|
const b = ledStripStore.colors[index + 2] || 0;
|
||||||
|
return `rgb(${r}, ${g}, ${b})`;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('🎨 LED: Generated colors', {
|
||||||
|
border: localProps.config.border,
|
||||||
|
colorsCount: colors.length,
|
||||||
|
sampleColors: colors.slice(0, 3)
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`Generated ${colors.length} colors for ${localProps.config.border}:`, colors.slice(0, 3));
|
|
||||||
setColors(colors);
|
setColors(colors);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -124,7 +143,7 @@ export const LedStripPart: Component<LedStripPartProps> = (props) => {
|
|||||||
{...rootProps}
|
{...rootProps}
|
||||||
ref={setAnchor}
|
ref={setAnchor}
|
||||||
class={
|
class={
|
||||||
'flex rounded-full flex-nowrap justify-around items-center overflow-hidden ' +
|
'flex rounded-full flex-nowrap justify-around items-center overflow-hidden bg-gray-800/20 border border-gray-600/30 min-h-[16px] min-w-[16px] ' +
|
||||||
rootProps.class
|
rootProps.class
|
||||||
}
|
}
|
||||||
classList={{
|
classList={{
|
||||||
|
@ -16,7 +16,7 @@ import {
|
|||||||
} from 'solid-js';
|
} from 'solid-js';
|
||||||
import { LedStripConfig, LedStripPixelMapper } from '../../models/led-strip-config';
|
import { LedStripConfig, LedStripPixelMapper } from '../../models/led-strip-config';
|
||||||
import { ledStripStore } from '../../stores/led-strip.store';
|
import { ledStripStore } from '../../stores/led-strip.store';
|
||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { LedStripConfigurationContext } from '../../contexts/led-strip-configuration.context';
|
import { LedStripConfigurationContext } from '../../contexts/led-strip-configuration.context';
|
||||||
import background from '../../assets/transparent-grid-background.svg?url';
|
import background from '../../assets/transparent-grid-background.svg?url';
|
||||||
|
|
||||||
|
@ -17,6 +17,10 @@ export const ScreenView: Component<ScreenViewProps> = (props) => {
|
|||||||
let canvas: HTMLCanvasElement;
|
let canvas: HTMLCanvasElement;
|
||||||
let root: HTMLDivElement;
|
let root: HTMLDivElement;
|
||||||
const [ctx, setCtx] = createSignal<CanvasRenderingContext2D | null>(null);
|
const [ctx, setCtx] = createSignal<CanvasRenderingContext2D | null>(null);
|
||||||
|
|
||||||
|
// Cache temporary canvas for scaling
|
||||||
|
let tempCanvas: HTMLCanvasElement | null = null;
|
||||||
|
let tempCtx: CanvasRenderingContext2D | null = null;
|
||||||
const [drawInfo, setDrawInfo] = createSignal({
|
const [drawInfo, setDrawInfo] = createSignal({
|
||||||
drawX: 0,
|
drawX: 0,
|
||||||
drawY: 0,
|
drawY: 0,
|
||||||
@ -29,9 +33,134 @@ export const ScreenView: Component<ScreenViewProps> = (props) => {
|
|||||||
height: number;
|
height: number;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
const [hidden, setHidden] = createSignal(false);
|
const [hidden, setHidden] = createSignal(false);
|
||||||
|
const [isLoading, setIsLoading] = createSignal(false);
|
||||||
|
let isMounted = true;
|
||||||
|
|
||||||
|
// Fetch screenshot data from backend
|
||||||
|
const fetchScreenshot = async () => {
|
||||||
|
console.log('📸 FETCH: Starting screenshot fetch', {
|
||||||
|
isLoading: isLoading(),
|
||||||
|
isMounted,
|
||||||
|
hidden: hidden(),
|
||||||
|
timestamp: new Date().toLocaleTimeString()
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isLoading()) {
|
||||||
|
console.log('⏳ FETCH: Already loading, skipping');
|
||||||
|
return; // Skip if already loading
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const response = await fetch(`ambient-light://displays/${localProps.displayId}?width=400&height=225&t=${timestamp}`);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error('❌ FETCH: Screenshot fetch failed', response.status, response.statusText);
|
||||||
|
const errorText = await response.text();
|
||||||
|
console.error('❌ FETCH: Error response body:', errorText);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const width = parseInt(response.headers.get('X-Image-Width') || '400');
|
||||||
|
const height = parseInt(response.headers.get('X-Image-Height') || '225');
|
||||||
|
const arrayBuffer = await response.arrayBuffer();
|
||||||
|
const buffer = new Uint8ClampedArray(arrayBuffer);
|
||||||
|
const expectedSize = width * height * 4;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Validate buffer size
|
||||||
|
if (buffer.length !== expectedSize) {
|
||||||
|
console.error('❌ FETCH: Invalid buffer size!', {
|
||||||
|
received: buffer.length,
|
||||||
|
expected: expectedSize,
|
||||||
|
ratio: buffer.length / expectedSize
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('📊 FETCH: Setting image data', { width, height, bufferSize: buffer.length });
|
||||||
|
|
||||||
|
setImageData({
|
||||||
|
buffer,
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
});
|
||||||
|
|
||||||
|
// Use setTimeout to ensure the signal update has been processed
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('🖼️ FETCH: Triggering draw after data set');
|
||||||
|
draw(false);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
// Schedule next frame after rendering is complete
|
||||||
|
const shouldContinue = !hidden() && isMounted;
|
||||||
|
console.log('🔄 FETCH: Scheduling next frame', {
|
||||||
|
hidden: hidden(),
|
||||||
|
isMounted,
|
||||||
|
shouldContinue,
|
||||||
|
nextFrameDelay: '1000ms'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (shouldContinue) {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (isMounted) {
|
||||||
|
console.log('🔄 FETCH: Starting next frame');
|
||||||
|
fetchScreenshot();
|
||||||
|
} else {
|
||||||
|
console.log('❌ FETCH: Component unmounted, stopping loop');
|
||||||
|
}
|
||||||
|
}, 1000); // Wait 1 second before next frame
|
||||||
|
} else {
|
||||||
|
console.log('❌ FETCH: Loop stopped - component hidden or unmounted');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ FETCH: Error fetching screenshot:', error);
|
||||||
|
// Even on error, schedule next frame
|
||||||
|
const shouldContinueOnError = !hidden() && isMounted;
|
||||||
|
console.log('🔄 FETCH: Error recovery - scheduling next frame', {
|
||||||
|
error: error.message,
|
||||||
|
shouldContinue: shouldContinueOnError,
|
||||||
|
nextFrameDelay: '2000ms'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (shouldContinueOnError) {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (isMounted) {
|
||||||
|
console.log('🔄 FETCH: Retrying after error');
|
||||||
|
fetchScreenshot();
|
||||||
|
}
|
||||||
|
}, 2000); // Wait longer on error
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const resetSize = () => {
|
const resetSize = () => {
|
||||||
const aspectRatio = canvas.width / canvas.height;
|
console.log('📏 CANVAS: Resizing', {
|
||||||
|
rootClientWidth: root.clientWidth,
|
||||||
|
rootClientHeight: root.clientHeight,
|
||||||
|
oldCanvasWidth: canvas.width,
|
||||||
|
oldCanvasHeight: canvas.height
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set canvas size first
|
||||||
|
canvas.width = root.clientWidth;
|
||||||
|
canvas.height = root.clientHeight;
|
||||||
|
|
||||||
|
console.log('📏 CANVAS: Size set', {
|
||||||
|
newCanvasWidth: canvas.width,
|
||||||
|
newCanvasHeight: canvas.height
|
||||||
|
});
|
||||||
|
|
||||||
|
// Use a default aspect ratio if canvas dimensions are invalid
|
||||||
|
const aspectRatio = (canvas.width > 0 && canvas.height > 0)
|
||||||
|
? canvas.width / canvas.height
|
||||||
|
: 16 / 9; // Default 16:9 aspect ratio
|
||||||
|
|
||||||
const drawWidth = Math.round(
|
const drawWidth = Math.round(
|
||||||
Math.min(root.clientWidth, root.clientHeight * aspectRatio),
|
Math.min(root.clientWidth, root.clientHeight * aspectRatio),
|
||||||
@ -50,132 +179,114 @@ export const ScreenView: Component<ScreenViewProps> = (props) => {
|
|||||||
drawHeight,
|
drawHeight,
|
||||||
});
|
});
|
||||||
|
|
||||||
canvas.width = root.clientWidth;
|
|
||||||
canvas.height = root.clientHeight;
|
|
||||||
|
|
||||||
draw(true);
|
draw(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const draw = (cached: boolean = false) => {
|
const draw = (cached: boolean = false) => {
|
||||||
const { drawX, drawY } = drawInfo();
|
const { drawX, drawY, drawWidth, drawHeight } = drawInfo();
|
||||||
|
|
||||||
let _ctx = ctx();
|
let _ctx = ctx();
|
||||||
let raw = imageData();
|
let raw = imageData();
|
||||||
|
|
||||||
|
console.log('🖼️ DRAW: Called with', {
|
||||||
|
cached,
|
||||||
|
hasContext: !!_ctx,
|
||||||
|
hasImageData: !!raw,
|
||||||
|
imageDataSize: raw ? `${raw.width}x${raw.height}` : 'none',
|
||||||
|
drawInfo: { drawX, drawY, drawWidth, drawHeight },
|
||||||
|
canvasSize: `${canvas.width}x${canvas.height}`,
|
||||||
|
contextType: _ctx ? 'valid' : 'null',
|
||||||
|
rawBufferSize: raw ? raw.buffer.length : 0
|
||||||
|
});
|
||||||
|
|
||||||
if (_ctx && raw) {
|
if (_ctx && raw) {
|
||||||
|
console.log('🖼️ DRAW: Starting to draw image');
|
||||||
_ctx.clearRect(0, 0, canvas.width, canvas.height);
|
_ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
// Apply transparency effect for cached images if needed
|
||||||
|
let buffer = raw.buffer;
|
||||||
if (cached) {
|
if (cached) {
|
||||||
for (let i = 3; i < raw.buffer.length; i += 8) {
|
buffer = new Uint8ClampedArray(raw.buffer);
|
||||||
raw.buffer[i] = Math.floor(raw.buffer[i] * 0.7);
|
for (let i = 3; i < buffer.length; i += 4) {
|
||||||
|
buffer[i] = Math.floor(buffer[i] * 0.7);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const img = new ImageData(raw.buffer, raw.width, raw.height);
|
|
||||||
_ctx.putImageData(img, drawX, drawY);
|
try {
|
||||||
|
// Create ImageData and draw directly
|
||||||
|
const img = new ImageData(buffer, raw.width, raw.height);
|
||||||
|
|
||||||
|
// If the image size matches the draw size, use putImageData directly
|
||||||
|
if (raw.width === drawWidth && raw.height === drawHeight) {
|
||||||
|
console.log('🖼️ DRAW: Using putImageData directly');
|
||||||
|
_ctx.putImageData(img, drawX, drawY);
|
||||||
|
console.log('✅ DRAW: putImageData completed');
|
||||||
|
} else {
|
||||||
|
console.log('🖼️ DRAW: Using scaling with temp canvas');
|
||||||
|
// Otherwise, use cached temporary canvas for scaling
|
||||||
|
if (!tempCanvas || tempCanvas.width !== raw.width || tempCanvas.height !== raw.height) {
|
||||||
|
tempCanvas = document.createElement('canvas');
|
||||||
|
tempCanvas.width = raw.width;
|
||||||
|
tempCanvas.height = raw.height;
|
||||||
|
tempCtx = tempCanvas.getContext('2d');
|
||||||
|
console.log('🖼️ DRAW: Created new temp canvas');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tempCtx) {
|
||||||
|
tempCtx.putImageData(img, 0, 0);
|
||||||
|
_ctx.drawImage(tempCanvas, drawX, drawY, drawWidth, drawHeight);
|
||||||
|
console.log('✅ DRAW: Scaled drawing completed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ DRAW: Error in draw():', error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('❌ DRAW: Cannot draw - missing context or image data', {
|
||||||
|
hasContext: !!_ctx,
|
||||||
|
hasImageData: !!raw
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// get screenshot
|
|
||||||
createEffect(() => {
|
|
||||||
let stopped = false;
|
|
||||||
const frame = async () => {
|
|
||||||
const { drawWidth, drawHeight } = drawInfo();
|
|
||||||
|
|
||||||
// Skip if dimensions are not ready
|
|
||||||
if (drawWidth <= 0 || drawHeight <= 0) {
|
|
||||||
console.log('Skipping frame: invalid dimensions', { drawWidth, drawHeight });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = `ambient-light://displays/${localProps.displayId}?width=${drawWidth}&height=${drawHeight}`;
|
// Initialize canvas and resize observer
|
||||||
|
onMount(() => {
|
||||||
|
console.log('🚀 CANVAS: Component mounted');
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
console.log('🚀 CANVAS: Context obtained', !!context);
|
||||||
|
setCtx(context);
|
||||||
|
console.log('🚀 CANVAS: Context signal set');
|
||||||
|
|
||||||
console.log('Fetching screenshot:', url);
|
// Initial size setup
|
||||||
|
resetSize();
|
||||||
|
|
||||||
try {
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
const response = await fetch(url, {
|
|
||||||
mode: 'cors',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
console.error('Screenshot fetch failed:', response.status, response.statusText);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const buffer = await response.body?.getReader().read();
|
|
||||||
if (buffer?.value) {
|
|
||||||
console.log('Screenshot received, size:', buffer.value.length);
|
|
||||||
setImageData({
|
|
||||||
buffer: new Uint8ClampedArray(buffer?.value),
|
|
||||||
width: drawWidth,
|
|
||||||
height: drawHeight,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log('No screenshot data received');
|
|
||||||
setImageData(null);
|
|
||||||
}
|
|
||||||
draw();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Screenshot fetch error:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
while (!stopped) {
|
|
||||||
if (hidden()) {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
await frame();
|
|
||||||
|
|
||||||
// Add a small delay to prevent overwhelming the backend
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 33)); // ~30 FPS
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
onCleanup(() => {
|
|
||||||
stopped = true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// resize
|
|
||||||
createEffect(() => {
|
|
||||||
let resizeObserver: ResizeObserver;
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
setCtx(canvas.getContext('2d'));
|
|
||||||
|
|
||||||
// Initial size setup
|
|
||||||
resetSize();
|
resetSize();
|
||||||
|
|
||||||
resizeObserver = new ResizeObserver(() => {
|
|
||||||
resetSize();
|
|
||||||
});
|
|
||||||
resizeObserver.observe(root);
|
|
||||||
});
|
});
|
||||||
|
resizeObserver.observe(root);
|
||||||
|
|
||||||
|
// Start screenshot fetching after context is ready
|
||||||
|
console.log('🚀 SCREENSHOT: Starting screenshot fetching');
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('🚀 SCREENSHOT: Context ready, starting fetch');
|
||||||
|
fetchScreenshot(); // Initial fetch - will self-schedule subsequent frames
|
||||||
|
}, 100); // Small delay to ensure context is ready
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
|
isMounted = false; // Stop scheduling new frames
|
||||||
resizeObserver?.unobserve(root);
|
resizeObserver?.unobserve(root);
|
||||||
|
console.log('🧹 CLEANUP: Component unmounted');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// update hidden
|
|
||||||
createEffect(() => {
|
|
||||||
const hide = () => {
|
|
||||||
setHidden(true);
|
|
||||||
console.log('hide');
|
|
||||||
};
|
|
||||||
const show = () => {
|
|
||||||
setHidden(false);
|
|
||||||
console.log('show');
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener('focus', show);
|
|
||||||
window.addEventListener('blur', hide);
|
|
||||||
|
|
||||||
onCleanup(() => {
|
// Note: Removed window focus/blur logic as it was causing screenshot loop to stop
|
||||||
window.removeEventListener('focus', show);
|
// when user interacted with dev tools or other windows
|
||||||
window.removeEventListener('blur', hide);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -183,7 +294,15 @@ export const ScreenView: Component<ScreenViewProps> = (props) => {
|
|||||||
{...rootProps}
|
{...rootProps}
|
||||||
class={'overflow-hidden h-full w-full ' + rootProps.class}
|
class={'overflow-hidden h-full w-full ' + rootProps.class}
|
||||||
>
|
>
|
||||||
<canvas ref={canvas!} />
|
<canvas
|
||||||
|
ref={canvas!}
|
||||||
|
style={{
|
||||||
|
display: 'block',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
'background-color': '#f0f0f0'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
{rootProps.children}
|
{rootProps.children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -4,7 +4,7 @@ import { ColorCalibration, LedStripConfigContainer } from '../../models/led-stri
|
|||||||
import { ledStripStore, setLedStripStore } from '../../stores/led-strip.store';
|
import { ledStripStore, setLedStripStore } from '../../stores/led-strip.store';
|
||||||
import { ColorSlider } from './color-slider';
|
import { ColorSlider } from './color-slider';
|
||||||
import { TestColorsBg } from './test-colors-bg';
|
import { TestColorsBg } from './test-colors-bg';
|
||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { VsClose } from 'solid-icons/vs';
|
import { VsClose } from 'solid-icons/vs';
|
||||||
import { BiRegularReset } from 'solid-icons/bi';
|
import { BiRegularReset } from 'solid-icons/bi';
|
||||||
import transparentBg from '../../assets/transparent-grid-background.svg?url';
|
import transparentBg from '../../assets/transparent-grid-background.svg?url';
|
||||||
|
Reference in New Issue
Block a user