From 44888520cbb20485b4efaa3eddb37aaab0915f5a Mon Sep 17 00:00:00 2001 From: Phuoc NTB Date: Wed, 1 Jul 2026 12:43:09 +0700 Subject: [PATCH] tam --- .DS_Store | Bin 8196 -> 10244 bytes client/.DS_Store | Bin 8196 -> 10244 bytes client/app.go | 80 ++++++++- client/build/.DS_Store | Bin 12292 -> 12292 bytes client/build/darwin/Info.dev.plist | 6 + client/build/darwin/Info.plist | 7 + client/internal/blocker/blocker_darwin.go | 59 +++++-- client/internal/camera/camera_darwin.go | 46 +++++ client/internal/camera/camera_darwin.m | 166 ++++++++++++++++++ client/internal/camera/camera_other.go | 20 +++ client/internal/screen/screen_darwin.go | 105 +++++++++++ .../screen/{screen.go => screen_other.go} | 3 + client/internal/winapi/wifi_darwin.go | 32 +++- client/internal/winapi/wifi_darwin.m | 56 ++++++ client/internal/winapi/wifi_other.go | 15 ++ client/internal/winapi/wifi_windows.go | 15 ++ 16 files changed, 588 insertions(+), 22 deletions(-) create mode 100644 client/internal/camera/camera_darwin.go create mode 100644 client/internal/camera/camera_darwin.m create mode 100644 client/internal/camera/camera_other.go create mode 100644 client/internal/screen/screen_darwin.go rename client/internal/screen/{screen.go => screen_other.go} (93%) diff --git a/.DS_Store b/.DS_Store index c94ca090ad902a7979c306d567a9380f0a1e7f1e..929b4480f0003c0bef06666ffb2a37ba6bf6e2e0 100644 GIT binary patch delta 602 zcmZp1XbF&DU|?W$DortDU{C-uIe-{M3-C-V6q~50$jG%ZU^hP_*JK`nJsidchB^vH zCPtGl2>8}#CYKc?<>V&;wVzEY$jK}&F)+9@_E+Hu;B@-_ooRpZHonDk*nwR37pOatYlvA1-k(r#I7o1s@8c~v3Q37Jb3kad9 z3rbCiLkQJ}R2Bdgi6BJ4hPfA|rshSb7iFgSr==CAmP8=2BjW}5i<67;b8-Umi!)0y z^YbEOiZk-dJxfw^J@eA?fmW7frk48`fTW7UQj3a#G=`pt$i$+e#Ju|SRHw?=oW!Km z9RED0{F0LVT#!261D6n1ex^!I8m>A($b9p`4+QVHU$Oh8+w?8E!GW zVfe<#!YIb5#Hh_^!f4BA&*;JE$r#0$%$UKL$(RK;kAaC1LNmxhX(+{5yqQ<<7u&`f uaW+9_h?oL5kah(ni;acfnJ4qh1d1?05)0T1K!d<;oLnH&yIDe*nHd0j;*=Ty delta 109 zcmZn(XmOBWU|?W$DortDU;r^WfEYvza8E20o2aMA$hk3KH$NliWFCP%97dMrItoT6 yCX+7+_-?|@6n+Z?4ua+&B*sjfiJ@^O4g)c*6GStL87G)z6f`Ct#{w6R+vDyCi49F! zGZX17EG)E+X)G)(wDb?yO5~05xEFovFuLA3`S~DIh)OTR~93#9wN#UDjEd^Ks;Gr2dKr zJ%AA?Lkl#}p~3^XkRRR1Zd5*$Zlp8UXBQS1y6@zbj;8LKJv8}VHg(N*(&Bb4s*z)k!x|jQ z!{J{kSd@nnhp(H8su{K9(l1ePNM;KI=UUH^A=z{AsuKgRZ%Oh?J1zVTgBdg&PG(p5 zaubIH^Kc99!#dQV4Ttas-oq#O20!2@{3Zc1N#@8rStK{f61hk6Z_6P?B&lU^dg8^R5i zFms+a#dHj8Iei!}s^i9oFaR delta 138 zcmZn(XmOBWU|?W$DortDU;r^WfEYvza8E20o2aMA&jOMM@);OXlFEwL=WQru{i~9KdaxAo-okk#W&U1ilI+U`vUqXj$$n0f%Jl$;P zBy>e8dhy5)WvJt$fr>JQh3dH0dc~0H79$owaZB~F-gL9Ymy4y!D2 zof|2AhvqevvsLX8&0F-L<${tN>f_~T<^JhWZf&oQq|wupX0jUwrR;9a&?=c~)udLw z3l~cAu&Ekc*R$NzERD0mJ#&*c(iYuLvnscTE>7(C#Pfx+VU$^oruK?@S)qD{rMPAB zZ8wvv#ICaj-PLHLS!cCgRy9_O&;P({LyIqDJ7Bs6WQex_fHc}cZBOFw8vDuSW{q)1 zeZKg@?RHFp862EjunL)1x?nGkU9-2Qp4%To(SJL% U_DN{g5vPBO&g<|Y`M-7l00l)L^Z)<= delta 202 zcmZokXi1phFUrHfz`)GFAi%&-#8A$V$&i;*T;{x4kYh3PWCI-*K}Mi73lOt0q%b6c zlx$W|PG+9GPqh`K5hM!)9Na(+Vl120c%CvdvTdFu@{(!uZE*vR%?cdsEE5~lBz diff --git a/client/build/darwin/Info.dev.plist b/client/build/darwin/Info.dev.plist index 8496abb..abfbab4 100644 --- a/client/build/darwin/Info.dev.plist +++ b/client/build/darwin/Info.dev.plist @@ -66,5 +66,11 @@ NSLocationWhenInUseUsageDescription Ứng dụng cần quyền vị trí để xác thực mạng Wi-Fi phòng thi. + NSCameraUsageDescription + Ứng dụng cần quyền truy cập camera để giám sát thi và xác thực khuôn mặt sinh viên. + NSMicrophoneUsageDescription + Ứng dụng cần quyền truy cập microphone để giám sát âm thanh phòng thi. + NSScreenCaptureUsageDescription + Ứng dụng cần quyền chia sẻ màn hình để giám sát và ghi lại quá trình thi. diff --git a/client/build/darwin/Info.plist b/client/build/darwin/Info.plist index a4698d6..206712a 100644 --- a/client/build/darwin/Info.plist +++ b/client/build/darwin/Info.plist @@ -61,5 +61,12 @@ {{end}} NSLocationWhenInUseUsageDescription Ứng dụng cần quyền vị trí để xác thực mạng Wi-Fi phòng thi. + NSCameraUsageDescription + Ứng dụng cần quyền truy cập camera để giám sát thi và xác thực khuôn mặt sinh viên. + NSMicrophoneUsageDescription + Ứng dụng cần quyền truy cập microphone để giám sát âm thanh phòng thi. + NSScreenCaptureUsageDescription + Ứng dụng cần quyền chia sẻ màn hình để giám sát và ghi lại quá trình thi. + diff --git a/client/internal/blocker/blocker_darwin.go b/client/internal/blocker/blocker_darwin.go index 6d64af8..754c841 100644 --- a/client/internal/blocker/blocker_darwin.go +++ b/client/internal/blocker/blocker_darwin.go @@ -40,13 +40,27 @@ var systemAllowed = map[string]bool{ "simple_care_v1.0": true, } -func getVisibleProcesses() (map[uint32]string, error) { +type ProcessInfo struct { + Name string + BundleID string +} + +func getVisibleProcesses() (map[uint32]ProcessInfo, error) { script := `tell application "System Events" - set nameList to name of every process whose visible is true - set pidList to unix id of every process whose visible is true set out to "" - repeat with i from 1 to count of nameList - set out to out & item i of nameList & ":" & item i of pidList & "\n" + set procList to every process whose visible is true + repeat with p in procList + try + set nameStr to name of p + set pidVal to unix id of p + set bid to bundle identifier of p + if bid is missing value then + set bid to "" + end if + set out to out & nameStr & "|" & pidVal & "|" & bid & "\n" + on error + -- ignore + end try end repeat return out end tell` @@ -55,22 +69,29 @@ end tell` if err != nil { return nil, err } - procs := make(map[uint32]string) + procs := make(map[uint32]ProcessInfo) lines := strings.Split(string(out), "\n") for _, line := range lines { line = strings.TrimSpace(line) if line == "" { continue } - idx := strings.LastIndex(line, ":") - if idx == -1 { + parts := strings.Split(line, "|") + if len(parts) < 2 { continue } - pName := line[:idx] - pIdStr := line[idx+1:] + pName := parts[0] + pIdStr := parts[1] + bundleID := "" + if len(parts) >= 3 { + bundleID = parts[2] + } var pid uint32 if _, err := fmt.Sscanf(pIdStr, "%d", &pid); err == nil { - procs[pid] = pName + procs[pid] = ProcessInfo{ + Name: pName, + BundleID: bundleID, + } } } return procs, nil @@ -98,11 +119,13 @@ func (b *Blocker) checkAndKill() { } myPid := uint32(os.Getpid()) - for pid, name := range procs { - pNameLower := strings.ToLower(name) + for pid, info := range procs { + pNameLower := strings.ToLower(info.Name) + bundleIDLower := strings.ToLower(info.BundleID) - // 1. Always allow our app, system/critical developer tools, or agent helpers - if pid == myPid || (currentExec != "" && pNameLower == currentExec) || systemAllowed[pNameLower] || strings.Contains(pNameLower, "antigravity") { + // 1. Always allow our app, system/critical developer tools, or agent helpers (and Antigravity IDE) + isAntigravity := strings.Contains(pNameLower, "antigravity") || strings.Contains(bundleIDLower, "antigravity") + if pid == myPid || (currentExec != "" && pNameLower == currentExec) || systemAllowed[pNameLower] || isAntigravity { continue } @@ -118,14 +141,14 @@ func (b *Blocker) checkAndKill() { // 3. If not allowed, kill the application if !allowed { if b.OnBlocked != nil { - b.OnBlocked(name, name) + b.OnBlocked(info.Name, info.Name) } - log.Printf("[BLOCKER] KILLED unauthorized application: %s (PID: %d)", name, pid) + log.Printf("[BLOCKER] KILLED unauthorized application: %s (PID: %d)", info.Name, pid) proc, err := os.FindProcess(int(pid)) if err == nil { errKill := proc.Kill() if errKill == nil && b.OnKill != nil { - b.OnKill(name, name) + b.OnKill(info.Name, info.Name) } } } diff --git a/client/internal/camera/camera_darwin.go b/client/internal/camera/camera_darwin.go new file mode 100644 index 0000000..b4fc4b7 --- /dev/null +++ b/client/internal/camera/camera_darwin.go @@ -0,0 +1,46 @@ +//go:build darwin + +package camera + +/* +#cgo LDFLAGS: -framework AVFoundation -framework Foundation -framework CoreImage -framework CoreMedia -framework CoreVideo -framework AppKit +#include + +int StartNativeCamera(void); +void StopNativeCamera(void); +char* GetLatestCameraFrame(void); +*/ +import "C" +import ( + "fmt" + "log" + "unsafe" +) + +// StartCapture starts native camera capture via AVCaptureSession +func StartCapture() error { + ret := C.StartNativeCamera() + if ret != 0 { + log.Println("[CAMERA] Failed to start native camera capture") + return fmt.Errorf("failed to start camera (code %d)", int(ret)) + } + log.Println("[CAMERA] Native camera capture started successfully") + return nil +} + +// StopCapture stops native camera capture +func StopCapture() { + C.StopNativeCamera() + log.Println("[CAMERA] Native camera capture stopped") +} + +// GetFrame returns the latest camera frame as a data:image/jpeg;base64,... string +// Returns empty string if no frame is available +func GetFrame() string { + cstr := C.GetLatestCameraFrame() + if cstr == nil { + return "" + } + defer C.free(unsafe.Pointer(cstr)) + return C.GoString(cstr) +} diff --git a/client/internal/camera/camera_darwin.m b/client/internal/camera/camera_darwin.m new file mode 100644 index 0000000..dc9c0de --- /dev/null +++ b/client/internal/camera/camera_darwin.m @@ -0,0 +1,166 @@ +#import +#import +#import +#import +#include +#include + +// ── Shared state ────────────────────────────────────────────── +static AVCaptureSession *captureSession = nil; +static AVCaptureVideoDataOutput *videoOutput = nil; +static dispatch_queue_t captureQueue = nil; + +// Latest frame stored as JPEG base64 +static NSString *latestFrameBase64 = nil; +static NSLock *frameLock = nil; + +// ── Delegate that receives sample buffers ───────────────────── +@interface CameraFrameDelegate : NSObject +@end + +@implementation CameraFrameDelegate + +- (void)captureOutput:(AVCaptureOutput *)output + didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer + fromConnection:(AVCaptureConnection *)connection { + + CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); + if (!imageBuffer) return; + + CIImage *ciImage = [CIImage imageWithCVPixelBuffer:imageBuffer]; + CIContext *context = [CIContext contextWithOptions:nil]; + CGImageRef cgImage = [context createCGImage:ciImage fromRect:ciImage.extent]; + if (!cgImage) return; + + // Convert to JPEG NSData (quality ≈ 0.4) + NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithCGImage:cgImage]; + CGImageRelease(cgImage); + + NSDictionary *props = @{NSImageCompressionFactor: @(0.4)}; + NSData *jpegData = [rep representationUsingType:NSBitmapImageFileTypeJPEG properties:props]; + if (!jpegData) return; + + NSString *b64 = [jpegData base64EncodedStringWithOptions:0]; + NSString *dataUrl = [NSString stringWithFormat:@"data:image/jpeg;base64,%@", b64]; + + [frameLock lock]; + latestFrameBase64 = dataUrl; + [frameLock unlock]; +} + +@end + +static CameraFrameDelegate *frameDelegate = nil; + +// ── C-exported functions ────────────────────────────────────── + +// StartNativeCamera: returns 0 on success, -1 on failure +int StartNativeCamera(void) { + if (captureSession && captureSession.isRunning) { + return 0; // already running + } + + if (!frameLock) { + frameLock = [[NSLock alloc] init]; + } + + // Check camera permission + if (@available(macOS 10.14, *)) { + AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; + if (status == AVAuthorizationStatusDenied || status == AVAuthorizationStatusRestricted) { + NSLog(@"[CAMERA] Camera permission denied (status=%ld)", (long)status); + return -1; + } + if (status == AVAuthorizationStatusNotDetermined) { + // Request permission synchronously via semaphore + dispatch_semaphore_t sem = dispatch_semaphore_create(0); + __block BOOL granted = NO; + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL g) { + granted = g; + dispatch_semaphore_signal(sem); + }]; + dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC)); + if (!granted) { + NSLog(@"[CAMERA] Camera permission not granted"); + return -1; + } + } + } + + captureSession = [[AVCaptureSession alloc] init]; + captureSession.sessionPreset = AVCaptureSessionPresetLow; // 320×240-ish + + // Find default video device + AVCaptureDevice *camera = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + if (!camera) { + NSLog(@"[CAMERA] No camera device found"); + captureSession = nil; + return -1; + } + + NSError *error = nil; + AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:camera error:&error]; + if (error || !input) { + NSLog(@"[CAMERA] Cannot create camera input: %@", error); + captureSession = nil; + return -1; + } + + if ([captureSession canAddInput:input]) { + [captureSession addInput:input]; + } else { + NSLog(@"[CAMERA] Cannot add camera input to session"); + captureSession = nil; + return -1; + } + + // Video data output + videoOutput = [[AVCaptureVideoDataOutput alloc] init]; + videoOutput.videoSettings = @{ + (NSString *)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA) + }; + videoOutput.alwaysDiscardsLateVideoFrames = YES; + + captureQueue = dispatch_queue_create("com.simplecare.camera", DISPATCH_QUEUE_SERIAL); + frameDelegate = [[CameraFrameDelegate alloc] init]; + [videoOutput setSampleBufferDelegate:frameDelegate queue:captureQueue]; + + if ([captureSession canAddOutput:videoOutput]) { + [captureSession addOutput:videoOutput]; + } else { + NSLog(@"[CAMERA] Cannot add video output to session"); + captureSession = nil; + return -1; + } + + [captureSession startRunning]; + NSLog(@"[CAMERA] Native camera capture started"); + return 0; +} + +void StopNativeCamera(void) { + if (captureSession && captureSession.isRunning) { + [captureSession stopRunning]; + NSLog(@"[CAMERA] Native camera capture stopped"); + } + captureSession = nil; + videoOutput = nil; + frameDelegate = nil; + + [frameLock lock]; + latestFrameBase64 = nil; + [frameLock unlock]; +} + +// GetLatestCameraFrame: returns a C-string (caller must free) or NULL +char* GetLatestCameraFrame(void) { + [frameLock lock]; + NSString *frame = latestFrameBase64; + latestFrameBase64 = nil; // consume + [frameLock unlock]; + + if (!frame || frame.length == 0) { + return NULL; + } + return strdup([frame UTF8String]); +} diff --git a/client/internal/camera/camera_other.go b/client/internal/camera/camera_other.go new file mode 100644 index 0000000..104b975 --- /dev/null +++ b/client/internal/camera/camera_other.go @@ -0,0 +1,20 @@ +//go:build !darwin + +package camera + +import "log" + +// StartCapture is a no-op on non-darwin platforms (Windows uses different webcam API) +func StartCapture() error { + log.Println("[CAMERA] Native camera capture not supported on this platform") + return nil +} + +// StopCapture is a no-op on non-darwin platforms +func StopCapture() { +} + +// GetFrame always returns empty on non-darwin platforms +func GetFrame() string { + return "" +} diff --git a/client/internal/screen/screen_darwin.go b/client/internal/screen/screen_darwin.go new file mode 100644 index 0000000..fb064d4 --- /dev/null +++ b/client/internal/screen/screen_darwin.go @@ -0,0 +1,105 @@ +//go:build darwin + +package screen + +/* +#cgo CFLAGS: -x objective-c -Wno-deprecated-declarations -Wno-unguarded-availability-new +#cgo LDFLAGS: -framework CoreGraphics -framework Foundation -framework AppKit + +// Disable availability checks - we handle this at runtime +#define __API_UNAVAILABLE(...) +#define API_UNAVAILABLE(...) + +#import +#import +#include +#include +#include + +// Use dlsym to call CGWindowListCreateImage dynamically to bypass macOS 15 availability check +typedef CGImageRef (*CGWindowListCreateImageFunc)(CGRect, CGWindowListOption, CGWindowID, CGWindowImageOption); + +static unsigned char* CaptureFullDesktop(int* outLen, int quality) { + // Dynamically load CGWindowListCreateImage to bypass compile-time availability check + static CGWindowListCreateImageFunc createImageFunc = NULL; + if (!createImageFunc) { + void *handle = dlopen("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics", RTLD_LAZY); + if (handle) { + createImageFunc = (CGWindowListCreateImageFunc)dlsym(handle, "CGWindowListCreateImage"); + } + } + + if (!createImageFunc) { + *outLen = 0; + return NULL; + } + + CGImageRef image = createImageFunc( + CGRectInfinite, + kCGWindowListOptionOnScreenOnly, + kCGNullWindowID, + kCGWindowImageDefault + ); + + if (!image) { + *outLen = 0; + return NULL; + } + + @autoreleasepool { + NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithCGImage:image]; + CGImageRelease(image); + + if (!rep) { + *outLen = 0; + return NULL; + } + + float q = (float)quality / 100.0f; + if (q < 0.1f) q = 0.1f; + if (q > 1.0f) q = 1.0f; + + NSDictionary *props = @{NSImageCompressionFactor: @(q)}; + NSData *jpegData = [rep representationUsingType:NSBitmapImageFileTypeJPEG properties:props]; + + if (!jpegData || jpegData.length == 0) { + *outLen = 0; + return NULL; + } + + *outLen = (int)jpegData.length; + unsigned char *buf = (unsigned char*)malloc(jpegData.length); + memcpy(buf, jpegData.bytes, jpegData.length); + return buf; + } +} +*/ +import "C" +import ( + "encoding/base64" + "fmt" + "log" + "unsafe" +) + +// CaptureScreen captures the full desktop on macOS using CGWindowListCreateImage (via dlsym) +// Returns a data:image/jpeg;base64,... string +func CaptureScreen() (string, error) { + var outLen C.int + quality := C.int(50) // JPEG quality 50% + + buf := C.CaptureFullDesktop(&outLen, quality) + if buf == nil || int(outLen) == 0 { + return "", fmt.Errorf("failed to capture screen: CGWindowListCreateImage returned nil") + } + defer C.free(unsafe.Pointer(buf)) + + jpegBytes := C.GoBytes(unsafe.Pointer(buf), outLen) + + if len(jpegBytes) < 100 { + log.Printf("[SCREEN] Warning: captured image is very small (%d bytes), may indicate permission issue", len(jpegBytes)) + } + + encoded := base64.StdEncoding.EncodeToString(jpegBytes) + return "data:image/jpeg;base64," + encoded, nil +} diff --git a/client/internal/screen/screen.go b/client/internal/screen/screen_other.go similarity index 93% rename from client/internal/screen/screen.go rename to client/internal/screen/screen_other.go index c98f9ca..a0f52bb 100644 --- a/client/internal/screen/screen.go +++ b/client/internal/screen/screen_other.go @@ -1,3 +1,5 @@ +//go:build !darwin + package screen import ( @@ -10,6 +12,7 @@ import ( ) // CaptureScreen chụp màn hình chính và trả về chuỗi Base64 dạng "data:image/jpeg;base64,..." +// Non-darwin: dùng kbinani/screenshot func CaptureScreen() (string, error) { n := screenshot.NumActiveDisplays() if n <= 0 { diff --git a/client/internal/winapi/wifi_darwin.go b/client/internal/winapi/wifi_darwin.go index 21cea90..3db68a6 100644 --- a/client/internal/winapi/wifi_darwin.go +++ b/client/internal/winapi/wifi_darwin.go @@ -3,7 +3,7 @@ package winapi /* -#cgo LDFLAGS: -framework CoreWLAN -framework CoreLocation -framework Foundation +#cgo LDFLAGS: -framework CoreWLAN -framework CoreLocation -framework Foundation -framework AVFoundation -framework CoreGraphics #include typedef struct { @@ -12,7 +12,12 @@ typedef struct { } CWifiInfo; void RequestLocationPermission(); +void RequestCameraAndMicPermission(); +void RequestScreenCapturePermission(); CWifiInfo GetCurrentWifiInfo(); +int GetCameraPermissionStatus(); +int GetMicrophonePermissionStatus(); +int GetScreenCapturePermissionStatus(); */ import "C" import ( @@ -24,6 +29,31 @@ func RequestLocationAccess() { C.RequestLocationPermission() } +// RequestCameraAndMicAccess requests Camera and Microphone permissions on macOS +func RequestCameraAndMicAccess() { + C.RequestCameraAndMicPermission() +} + +// RequestScreenCaptureAccess requests Screen Capture permission on macOS +func RequestScreenCaptureAccess() { + C.RequestScreenCapturePermission() +} + +// GetCameraPermission status on macOS +func GetCameraPermission() int { + return int(C.GetCameraPermissionStatus()) +} + +// GetMicrophonePermission status on macOS +func GetMicrophonePermission() int { + return int(C.GetMicrophonePermissionStatus()) +} + +// GetScreenCapturePermission status on macOS +func GetScreenCapturePermission() int { + return int(C.GetScreenCapturePermissionStatus()) +} + // GetWifiConnection đọc SSID và BSSID từ CoreWLAN (macOS) func GetWifiConnection() WifiConnection { info := C.GetCurrentWifiInfo() diff --git a/client/internal/winapi/wifi_darwin.m b/client/internal/winapi/wifi_darwin.m index e9f8901..0bea050 100644 --- a/client/internal/winapi/wifi_darwin.m +++ b/client/internal/winapi/wifi_darwin.m @@ -1,5 +1,7 @@ #import #import +#import +#import #import static CLLocationManager *locationManager = nil; @@ -15,6 +17,60 @@ void RequestLocationPermission() { }); } +void RequestCameraAndMicPermission() { + dispatch_async(dispatch_get_main_queue(), ^{ + if (@available(macOS 10.14, *)) { + // Request Camera + AVAuthorizationStatus cameraStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; + if (cameraStatus == AVAuthorizationStatusNotDetermined) { + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) { + // Camera permission requested + }]; + } + + // Request Microphone + AVAuthorizationStatus micStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio]; + if (micStatus == AVAuthorizationStatusNotDetermined) { + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) { + // Mic permission requested + }]; + } + } + }); +} + +void RequestScreenCapturePermission() { + dispatch_async(dispatch_get_main_queue(), ^{ + if (@available(macOS 11.0, *)) { + BOOL hasAccess = CGPreflightScreenCaptureAccess(); + if (!hasAccess) { + CGRequestScreenCaptureAccess(); + } + } + }); +} + +int GetCameraPermissionStatus() { + if (@available(macOS 10.14, *)) { + return (int)[AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; + } + return -1; +} + +int GetMicrophonePermissionStatus() { + if (@available(macOS 10.14, *)) { + return (int)[AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio]; + } + return -1; +} + +int GetScreenCapturePermissionStatus() { + if (@available(macOS 11.0, *)) { + return CGPreflightScreenCaptureAccess() ? 1 : 0; + } + return -1; +} + typedef struct { char* ssid; char* bssid; diff --git a/client/internal/winapi/wifi_other.go b/client/internal/winapi/wifi_other.go index f823f62..61dde62 100644 --- a/client/internal/winapi/wifi_other.go +++ b/client/internal/winapi/wifi_other.go @@ -9,3 +9,18 @@ func GetWifiConnection() WifiConnection { // RequestLocationAccess requests Location permission (stub for other systems) func RequestLocationAccess() {} + +// RequestCameraAndMicAccess stub +func RequestCameraAndMicAccess() {} + +// RequestScreenCaptureAccess stub +func RequestScreenCaptureAccess() {} + +// GetCameraPermission stub +func GetCameraPermission() int { return -1 } + +// GetMicrophonePermission stub +func GetMicrophonePermission() int { return -1 } + +// GetScreenCapturePermission stub +func GetScreenCapturePermission() int { return -1 } diff --git a/client/internal/winapi/wifi_windows.go b/client/internal/winapi/wifi_windows.go index 4400976..6f738f9 100644 --- a/client/internal/winapi/wifi_windows.go +++ b/client/internal/winapi/wifi_windows.go @@ -41,3 +41,18 @@ func GetWifiConnection() WifiConnection { // RequestLocationAccess requests Location permission (stub for Windows) func RequestLocationAccess() {} + +// RequestCameraAndMicAccess stub +func RequestCameraAndMicAccess() {} + +// RequestScreenCaptureAccess stub +func RequestScreenCaptureAccess() {} + +// GetCameraPermission stub +func GetCameraPermission() int { return -1 } + +// GetMicrophonePermission stub +func GetMicrophonePermission() int { return -1 } + +// GetScreenCapturePermission stub +func GetScreenCapturePermission() int { return -1 }