This commit is contained in:
2026-07-01 12:43:09 +07:00
parent 0d1e240227
commit 44888520cb
16 changed files with 588 additions and 22 deletions

BIN
.DS_Store vendored

Binary file not shown.

BIN
client/.DS_Store vendored

Binary file not shown.

View File

@@ -25,6 +25,7 @@ import (
"time" "time"
"client/internal/blocker" "client/internal/blocker"
"client/internal/camera"
"client/internal/guard" "client/internal/guard"
"client/internal/screen" "client/internal/screen"
"client/internal/winapi" "client/internal/winapi"
@@ -124,6 +125,8 @@ type App struct {
lastSyncTime time.Time lastSyncTime time.Time
isStreamingSc bool isStreamingSc bool
streamScStop chan struct{} streamScStop chan struct{}
isStreamingCam bool
streamCamStop chan struct{}
statusMsg string statusMsg string
expectingLogin bool expectingLogin bool
needsClearPortalStorage bool needsClearPortalStorage bool
@@ -212,6 +215,13 @@ func (a *App) startup(ctx context.Context) {
// Request Location Access (macOS) // Request Location Access (macOS)
winapi.RequestLocationAccess() winapi.RequestLocationAccess()
winapi.RequestCameraAndMicAccess()
winapi.RequestScreenCaptureAccess()
// Log initial permission statuses
log.Printf("[PERMISSIONS] Camera Status: %d (0=NotDetermined, 1=Restricted, 2=Denied, 3=Authorized)", winapi.GetCameraPermission())
log.Printf("[PERMISSIONS] Microphone Status: %d (0=NotDetermined, 1=Restricted, 2=Denied, 3=Authorized)", winapi.GetMicrophonePermission())
log.Printf("[PERMISSIONS] Screen Capture Status: %d (0=NoAccess, 1=Authorized, -1=NotSupported)", winapi.GetScreenCapturePermission())
// Register blocker callbacks // Register blocker callbacks
blocker.Instance.OnBlocked = func(procName string, title string) { blocker.Instance.OnBlocked = func(procName string, title string) {
@@ -261,7 +271,7 @@ func (a *App) tearDownBeforeQuit() {
blocker.Instance.Stop() blocker.Instance.Stop()
a.stopScreenshotStream() a.stopScreenshotStream()
runtime.EventsEmit(a.ctx, "stop_webcam_stream") a.stopWebcamStream()
a.disconnectWS() a.disconnectWS()
guard.Stop() guard.Stop()
} }
@@ -1046,6 +1056,7 @@ func (a *App) fetchAllowedApps(classId int64) {
} }
a.mu.Unlock() a.mu.Unlock()
log.Printf("[CLIENT] Allowed apps fetched from server: %s", res.Keywords)
blocker.Instance.SetKeywords(res.Keywords) blocker.Instance.SetKeywords(res.Keywords)
blocker.Instance.Start() blocker.Instance.Start()
} }
@@ -1141,9 +1152,9 @@ func (a *App) connectWS() {
case "stop_screenshot_stream": case "stop_screenshot_stream":
a.stopScreenshotStream() a.stopScreenshotStream()
case "start_webcam_stream": case "start_webcam_stream":
runtime.EventsEmit(a.ctx, "start_webcam_stream") a.startWebcamStream()
case "stop_webcam_stream": case "stop_webcam_stream":
runtime.EventsEmit(a.ctx, "stop_webcam_stream") a.stopWebcamStream()
case "chat:message": case "chat:message":
senderRole, _ := msg.Data["senderRole"].(string) senderRole, _ := msg.Data["senderRole"].(string)
if senderRole == "staff" { if senderRole == "staff" {
@@ -1244,6 +1255,69 @@ func (a *App) stopScreenshotStream() {
log.Println("[WS] Screenshot screen-streaming stopped.") log.Println("[WS] Screenshot screen-streaming stopped.")
} }
func (a *App) startWebcamStream() {
a.mu.Lock()
if a.isStreamingCam {
a.mu.Unlock()
return
}
a.isStreamingCam = true
a.streamCamStop = make(chan struct{})
a.mu.Unlock()
if err := camera.StartCapture(); err != nil {
log.Printf("[WS] Failed to start native camera: %v", err)
a.mu.Lock()
a.isStreamingCam = false
a.mu.Unlock()
return
}
go func() {
ticker := time.NewTicker(250 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ticker.C:
frame := camera.GetFrame()
if frame == "" {
continue
}
a.mu.Lock()
conn := a.wsConn
a.mu.Unlock()
if conn != nil {
_ = conn.WriteJSON(map[string]any{
"event": "webcam_stream_frame",
"data": map[string]any{
"imageBuffer": frame,
},
})
}
case <-a.streamCamStop:
return
}
}
}()
log.Println("[WS] Webcam native streaming started.")
}
func (a *App) stopWebcamStream() {
a.mu.Lock()
defer a.mu.Unlock()
if !a.isStreamingCam {
return
}
a.isStreamingCam = false
close(a.streamCamStop)
camera.StopCapture()
log.Println("[WS] Webcam native streaming stopped.")
}
func (a *App) alertChatIncoming(from, preview string) { func (a *App) alertChatIncoming(from, preview string) {
if preview == "" { if preview == "" {
preview = "Bạn có tin nhắn mới" preview = "Bạn có tin nhắn mới"

BIN
client/build/.DS_Store vendored

Binary file not shown.

View File

@@ -66,5 +66,11 @@
</dict> </dict>
<key>NSLocationWhenInUseUsageDescription</key> <key>NSLocationWhenInUseUsageDescription</key>
<string>Ứng dụng cần quyền vị trí để xác thực mạng Wi-Fi phòng thi.</string> <string>Ứng dụng cần quyền vị trí để xác thực mạng Wi-Fi phòng thi.</string>
<key>NSCameraUsageDescription</key>
<string>Ứ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.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Ứng dụng cần quyền truy cập microphone để giám sát âm thanh phòng thi.</string>
<key>NSScreenCaptureUsageDescription</key>
<string>Ứ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.</string>
</dict> </dict>
</plist> </plist>

View File

@@ -61,5 +61,12 @@
{{end}} {{end}}
<key>NSLocationWhenInUseUsageDescription</key> <key>NSLocationWhenInUseUsageDescription</key>
<string>Ứng dụng cần quyền vị trí để xác thực mạng Wi-Fi phòng thi.</string> <string>Ứng dụng cần quyền vị trí để xác thực mạng Wi-Fi phòng thi.</string>
<key>NSCameraUsageDescription</key>
<string>Ứ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.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Ứng dụng cần quyền truy cập microphone để giám sát âm thanh phòng thi.</string>
<key>NSScreenCaptureUsageDescription</key>
<string>Ứ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.</string>
</dict> </dict>
</plist> </plist>

View File

@@ -40,13 +40,27 @@ var systemAllowed = map[string]bool{
"simple_care_v1.0": true, "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" 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 "" set out to ""
repeat with i from 1 to count of nameList set procList to every process whose visible is true
set out to out & item i of nameList & ":" & item i of pidList & "\n" 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 end repeat
return out return out
end tell` end tell`
@@ -55,22 +69,29 @@ end tell`
if err != nil { if err != nil {
return nil, err return nil, err
} }
procs := make(map[uint32]string) procs := make(map[uint32]ProcessInfo)
lines := strings.Split(string(out), "\n") lines := strings.Split(string(out), "\n")
for _, line := range lines { for _, line := range lines {
line = strings.TrimSpace(line) line = strings.TrimSpace(line)
if line == "" { if line == "" {
continue continue
} }
idx := strings.LastIndex(line, ":") parts := strings.Split(line, "|")
if idx == -1 { if len(parts) < 2 {
continue continue
} }
pName := line[:idx] pName := parts[0]
pIdStr := line[idx+1:] pIdStr := parts[1]
bundleID := ""
if len(parts) >= 3 {
bundleID = parts[2]
}
var pid uint32 var pid uint32
if _, err := fmt.Sscanf(pIdStr, "%d", &pid); err == nil { if _, err := fmt.Sscanf(pIdStr, "%d", &pid); err == nil {
procs[pid] = pName procs[pid] = ProcessInfo{
Name: pName,
BundleID: bundleID,
}
} }
} }
return procs, nil return procs, nil
@@ -98,11 +119,13 @@ func (b *Blocker) checkAndKill() {
} }
myPid := uint32(os.Getpid()) myPid := uint32(os.Getpid())
for pid, name := range procs { for pid, info := range procs {
pNameLower := strings.ToLower(name) pNameLower := strings.ToLower(info.Name)
bundleIDLower := strings.ToLower(info.BundleID)
// 1. Always allow our app, system/critical developer tools, or agent helpers // 1. Always allow our app, system/critical developer tools, or agent helpers (and Antigravity IDE)
if pid == myPid || (currentExec != "" && pNameLower == currentExec) || systemAllowed[pNameLower] || strings.Contains(pNameLower, "antigravity") { isAntigravity := strings.Contains(pNameLower, "antigravity") || strings.Contains(bundleIDLower, "antigravity")
if pid == myPid || (currentExec != "" && pNameLower == currentExec) || systemAllowed[pNameLower] || isAntigravity {
continue continue
} }
@@ -118,14 +141,14 @@ func (b *Blocker) checkAndKill() {
// 3. If not allowed, kill the application // 3. If not allowed, kill the application
if !allowed { if !allowed {
if b.OnBlocked != nil { 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)) proc, err := os.FindProcess(int(pid))
if err == nil { if err == nil {
errKill := proc.Kill() errKill := proc.Kill()
if errKill == nil && b.OnKill != nil { if errKill == nil && b.OnKill != nil {
b.OnKill(name, name) b.OnKill(info.Name, info.Name)
} }
} }
} }

View File

@@ -0,0 +1,46 @@
//go:build darwin
package camera
/*
#cgo LDFLAGS: -framework AVFoundation -framework Foundation -framework CoreImage -framework CoreMedia -framework CoreVideo -framework AppKit
#include <stdlib.h>
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)
}

View File

@@ -0,0 +1,166 @@
#import <AVFoundation/AVFoundation.h>
#import <Foundation/Foundation.h>
#import <CoreImage/CoreImage.h>
#import <AppKit/AppKit.h>
#include <stdlib.h>
#include <string.h>
// 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 <AVCaptureVideoDataOutputSampleBufferDelegate>
@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]);
}

View File

@@ -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 ""
}

View File

@@ -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 <CoreGraphics/CoreGraphics.h>
#import <AppKit/AppKit.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
// 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
}

View File

@@ -1,3 +1,5 @@
//go:build !darwin
package screen package screen
import ( 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,..." // 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) { func CaptureScreen() (string, error) {
n := screenshot.NumActiveDisplays() n := screenshot.NumActiveDisplays()
if n <= 0 { if n <= 0 {

View File

@@ -3,7 +3,7 @@
package winapi package winapi
/* /*
#cgo LDFLAGS: -framework CoreWLAN -framework CoreLocation -framework Foundation #cgo LDFLAGS: -framework CoreWLAN -framework CoreLocation -framework Foundation -framework AVFoundation -framework CoreGraphics
#include <stdlib.h> #include <stdlib.h>
typedef struct { typedef struct {
@@ -12,7 +12,12 @@ typedef struct {
} CWifiInfo; } CWifiInfo;
void RequestLocationPermission(); void RequestLocationPermission();
void RequestCameraAndMicPermission();
void RequestScreenCapturePermission();
CWifiInfo GetCurrentWifiInfo(); CWifiInfo GetCurrentWifiInfo();
int GetCameraPermissionStatus();
int GetMicrophonePermissionStatus();
int GetScreenCapturePermissionStatus();
*/ */
import "C" import "C"
import ( import (
@@ -24,6 +29,31 @@ func RequestLocationAccess() {
C.RequestLocationPermission() 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) // GetWifiConnection đọc SSID và BSSID từ CoreWLAN (macOS)
func GetWifiConnection() WifiConnection { func GetWifiConnection() WifiConnection {
info := C.GetCurrentWifiInfo() info := C.GetCurrentWifiInfo()

View File

@@ -1,5 +1,7 @@
#import <CoreWLAN/CoreWLAN.h> #import <CoreWLAN/CoreWLAN.h>
#import <CoreLocation/CoreLocation.h> #import <CoreLocation/CoreLocation.h>
#import <AVFoundation/AVFoundation.h>
#import <CoreGraphics/CoreGraphics.h>
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
static CLLocationManager *locationManager = nil; 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 { typedef struct {
char* ssid; char* ssid;
char* bssid; char* bssid;

View File

@@ -9,3 +9,18 @@ func GetWifiConnection() WifiConnection {
// RequestLocationAccess requests Location permission (stub for other systems) // RequestLocationAccess requests Location permission (stub for other systems)
func RequestLocationAccess() {} 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 }

View File

@@ -41,3 +41,18 @@ func GetWifiConnection() WifiConnection {
// RequestLocationAccess requests Location permission (stub for Windows) // RequestLocationAccess requests Location permission (stub for Windows)
func RequestLocationAccess() {} 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 }