11import { useState , useEffect } from 'react' ;
22import { useNavigate } from 'react-router-dom' ;
3- import { Users , Link2 } from 'lucide-react' ;
3+ import { Users , Link2 , Loader2 } from 'lucide-react' ;
44import { SourcePicker } from '@/components/capture/SourcePicker' ;
55import { CapturePreview } from '@/components/capture/CapturePreview' ;
66import { CreateLinkModal } from '@/components/CreateLinkModal' ;
@@ -17,6 +17,7 @@ export function HomePage() {
1717 const [ error , setError ] = useState < string | null > ( null ) ;
1818 const [ displayServer , setDisplayServer ] = useState < DisplayServer > ( 'x11' ) ;
1919 const [ isWayland , setIsWayland ] = useState ( false ) ;
20+ const [ isCapturing , setIsCapturing ] = useState ( false ) ;
2021 const [ showCreateLinkModal , setShowCreateLinkModal ] = useState ( false ) ;
2122 const [ preCreatedSession , setPreCreatedSession ] = useState < Session | null > ( null ) ;
2223
@@ -31,8 +32,12 @@ export function HomePage() {
3132 } , [ ] ) ;
3233
3334 const handleSourceSelect = async ( source : CaptureSource ) => {
35+ // Prevent multiple concurrent capture attempts
36+ if ( isCapturing ) return ;
37+
3438 setSelectedSource ( source ) ;
3539 setError ( null ) ;
40+ setIsCapturing ( true ) ;
3641
3742 try {
3843 // Stop existing stream
@@ -77,14 +82,24 @@ export function HomePage() {
7782 } catch ( err ) {
7883 console . error ( '[Renderer] Failed to start capture:' , err ) ;
7984 const message = err instanceof Error ? err . message : 'Unknown error' ;
80- setError ( `Failed to capture: ${ message } ` ) ;
85+ // Provide more user-friendly error messages
86+ if ( message . includes ( 'Permission denied' ) || message . includes ( 'NotAllowedError' ) ) {
87+ setError ( 'Screen capture was canceled or permission denied. Please try again.' ) ;
88+ } else {
89+ setError ( `Failed to capture: ${ message } ` ) ;
90+ }
8191 setSelectedSource ( null ) ;
92+ } finally {
93+ setIsCapturing ( false ) ;
8294 }
8395 } ;
8496
8597 // Handle Wayland direct capture (bypasses source picker)
8698 const handleWaylandCapture = async ( ) => {
99+ if ( isCapturing ) return ;
100+
87101 setError ( null ) ;
102+ setIsCapturing ( true ) ;
88103
89104 try {
90105 if ( stream ) {
@@ -116,7 +131,13 @@ export function HomePage() {
116131 } catch ( err ) {
117132 console . error ( '[Renderer] Failed to start Wayland capture:' , err ) ;
118133 const message = err instanceof Error ? err . message : 'Unknown error' ;
119- setError ( `Failed to capture: ${ message } ` ) ;
134+ if ( message . includes ( 'Permission denied' ) || message . includes ( 'NotAllowedError' ) ) {
135+ setError ( 'Screen capture was canceled or permission denied. Please try again.' ) ;
136+ } else {
137+ setError ( `Failed to capture: ${ message } ` ) ;
138+ }
139+ } finally {
140+ setIsCapturing ( false ) ;
120141 }
121142 } ;
122143
@@ -147,22 +168,36 @@ export function HomePage() {
147168 ) }
148169
149170 { ! stream ? (
150- < >
171+ < div className = "relative" >
172+ { /* Loading overlay */ }
173+ { isCapturing && (
174+ < div className = "absolute inset-0 z-10 flex items-center justify-center rounded-lg bg-background/80 backdrop-blur-sm" >
175+ < div className = "flex flex-col items-center gap-3" >
176+ < Loader2 className = "h-8 w-8 animate-spin text-primary" />
177+ < p className = "text-sm text-muted-foreground" >
178+ { isWayland ? 'Waiting for system screen picker...' : 'Starting capture...' }
179+ </ p >
180+ </ div >
181+ </ div >
182+ ) }
183+
151184 < div className = "mb-6 flex items-center justify-between" >
152185 < h1 className = "text-2xl font-semibold" > Select a screen or window to share</ h1 >
153186 < div className = "flex items-center gap-2" >
154187 < button
155188 onClick = { ( ) => {
156189 setShowCreateLinkModal ( true ) ;
157190 } }
158- className = "flex items-center gap-2 rounded-lg bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90"
191+ disabled = { isCapturing }
192+ className = "flex items-center gap-2 rounded-lg bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:opacity-50"
159193 >
160194 < Link2 className = "h-4 w-4" />
161195 Create Link
162196 </ button >
163197 < button
164198 onClick = { ( ) => void navigate ( '/join' ) }
165- className = "flex items-center gap-2 rounded-lg bg-muted px-4 py-2 text-sm font-medium text-foreground transition-colors hover:bg-muted/80"
199+ disabled = { isCapturing }
200+ className = "flex items-center gap-2 rounded-lg bg-muted px-4 py-2 text-sm font-medium text-foreground transition-colors hover:bg-muted/80 disabled:opacity-50"
166201 >
167202 < Users className = "h-4 w-4" />
168203 Join a Session
@@ -180,9 +215,17 @@ export function HomePage() {
180215 onClick = { ( ) => {
181216 void handleWaylandCapture ( ) ;
182217 } }
183- className = "rounded-lg bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90"
218+ disabled = { isCapturing }
219+ className = "rounded-lg bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:opacity-50"
184220 >
185- Open System Screen Picker
221+ { isCapturing ? (
222+ < >
223+ < Loader2 className = "mr-2 inline h-4 w-4 animate-spin" />
224+ Waiting...
225+ </ >
226+ ) : (
227+ 'Open System Screen Picker'
228+ ) }
186229 </ button >
187230 </ div >
188231 ) }
@@ -192,7 +235,7 @@ export function HomePage() {
192235 void handleSourceSelect ( source ) ;
193236 } }
194237 />
195- </ >
238+ </ div >
196239 ) : (
197240 < CapturePreview
198241 stream = { stream }
0 commit comments