This commit is contained in:
@@ -46,13 +46,13 @@ cd client
|
|||||||
chmod +x build.sh
|
chmod +x build.sh
|
||||||
./build.sh
|
./build.sh
|
||||||
```
|
```
|
||||||
Sau khi build xong, file thực thi sẽ nằm tại: `client/build/bin/simple_care_v1.1`
|
Sau khi build xong, file thực thi sẽ nằm tại: `client/build/bin/simple_care_v1.2`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Cho Người dùng cuối (End-user - Chỉ chạy ứng dụng)
|
## 2. Cho Người dùng cuối (End-user - Chỉ chạy ứng dụng)
|
||||||
|
|
||||||
Người dùng cuối **không cần** cài đặt Go, Node hay trình biên dịch C. Họ chỉ cần tệp thực thi `simple_care_v1.1` và cài đặt các thư viện đồ họa cơ bản của hệ thống:
|
Người dùng cuối **không cần** cài đặt Go, Node hay trình biên dịch C. Họ chỉ cần tệp thực thi `simple_care_v1.2` và cài đặt các thư viện đồ họa cơ bản của hệ thống:
|
||||||
|
|
||||||
* **Ubuntu / Debian / Linux Mint:**
|
* **Ubuntu / Debian / Linux Mint:**
|
||||||
```bash
|
```bash
|
||||||
@@ -73,8 +73,8 @@ Người dùng cuối **không cần** cài đặt Go, Node hay trình biên d
|
|||||||
### Lệnh chạy ứng dụng:
|
### Lệnh chạy ứng dụng:
|
||||||
Cấp quyền chạy cho file và khởi chạy:
|
Cấp quyền chạy cho file và khởi chạy:
|
||||||
```bash
|
```bash
|
||||||
chmod +x simple_care_v1.1
|
chmod +x simple_care_v1.2
|
||||||
./simple_care_v1.1
|
./simple_care_v1.2
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -306,12 +306,30 @@ func (a *App) HandleBeforeClose() (prevent bool) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selection, err := runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
|
||||||
|
Type: runtime.QuestionDialog,
|
||||||
|
Title: "Xác nhận thoát",
|
||||||
|
Message: "Bạn có chắc chắn muốn thoát ứng dụng không?\n\nLưu ý: Thoát ứng dụng khi đang trong giờ thi/học sẽ được ghi nhận là VI PHẠM.",
|
||||||
|
Buttons: []string{"Có, thoát ứng dụng", "Không, tiếp tục"},
|
||||||
|
DefaultButton: "Không, tiếp tục",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
a.reportViolation("app_closed", "Sinh viên tự đóng ứng dụng khi đang giám sát ("+mode+")")
|
a.reportViolation("app_closed", "Sinh viên tự đóng ứng dụng khi đang giám sát ("+mode+")")
|
||||||
a.tearDownBeforeQuit()
|
a.tearDownBeforeQuit()
|
||||||
a.clearRunLock()
|
a.clearRunLock()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if selection == "Có, thoát ứng dụng" {
|
||||||
|
a.reportViolation("app_closed", "Sinh viên tự đóng ứng dụng khi đang giám sát ("+mode+")")
|
||||||
|
a.tearDownBeforeQuit()
|
||||||
|
a.clearRunLock()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true // Prevent close!
|
||||||
|
}
|
||||||
|
|
||||||
// tearDownBeforeQuit ngắt mọi kênh giám sát ngay — trước khi hiện dialog (tránh treo OK để duy trì kết nối).
|
// tearDownBeforeQuit ngắt mọi kênh giám sát ngay — trước khi hiện dialog (tránh treo OK để duy trì kết nối).
|
||||||
func (a *App) tearDownBeforeQuit() {
|
func (a *App) tearDownBeforeQuit() {
|
||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
|
|||||||
4
client/frontend/package-lock.json
generated
4
client/frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "1.1.0",
|
"version": "1.2.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "1.1.0",
|
"version": "1.2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"pdfjs-dist": "^3.11.174"
|
"pdfjs-dist": "^3.11.174"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.1.0",
|
"version": "1.2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
39a3c87a78e46d8e7ef5f5042d044ee3
|
06a16f2c007333cdf273a92d4914c63b
|
||||||
@@ -19,7 +19,7 @@ if (typeof window.go.main.App === 'undefined') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const brandLogoHtml = `<img src="${logoUrl}" alt="Simple Care" class="brand-logo-img" />`;
|
const brandLogoHtml = `<img src="${logoUrl}" alt="Simple Care" class="brand-logo-img" />`;
|
||||||
const APP_VERSION = '1.1';
|
const APP_VERSION = '1.2';
|
||||||
|
|
||||||
// Trạng thái cục bộ
|
// Trạng thái cục bộ
|
||||||
let loggedIn = false;
|
let loggedIn = false;
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ var systemAllowed = map[string]bool{
|
|||||||
"client": true,
|
"client": true,
|
||||||
"simple_care_v1.0": true,
|
"simple_care_v1.0": true,
|
||||||
"simple_care_v1.1": true,
|
"simple_care_v1.1": true,
|
||||||
|
"simple_care_v1.2": true,
|
||||||
"simple_care": true,
|
"simple_care": true,
|
||||||
"wails": true,
|
"wails": true,
|
||||||
"code": true, // VSCode
|
"code": true, // VSCode
|
||||||
|
|||||||
@@ -237,6 +237,7 @@ var systemAllowed = map[string]bool{
|
|||||||
"client": true,
|
"client": true,
|
||||||
"simple_care_v1.0": true,
|
"simple_care_v1.0": true,
|
||||||
"simple_care_v1.1": true,
|
"simple_care_v1.1": true,
|
||||||
|
"simple_care_v1.2": true,
|
||||||
"simple_care": true,
|
"simple_care": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -330,6 +330,7 @@ var systemAllowed = map[string]bool{
|
|||||||
"client.exe": true,
|
"client.exe": true,
|
||||||
"simple_care_v1.0.exe": true,
|
"simple_care_v1.0.exe": true,
|
||||||
"simple_care_v1.1.exe": true,
|
"simple_care_v1.1.exe": true,
|
||||||
|
"simple_care_v1.2.exe": true,
|
||||||
"simple_care.exe": true,
|
"simple_care.exe": true,
|
||||||
"wails.exe": true,
|
"wails.exe": true,
|
||||||
"msedgewebview2.exe": true, // WebView2 runtime (Wails renderer)
|
"msedgewebview2.exe": true, // WebView2 runtime (Wails renderer)
|
||||||
|
|||||||
@@ -47,8 +47,53 @@ static unsigned char* CaptureFullDesktop(int* outLen, int quality) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithCGImage:image];
|
// Calculate new size maintaining aspect ratio (limiting to max 1024x768)
|
||||||
|
CGFloat originalWidth = CGImageGetWidth(image);
|
||||||
|
CGFloat originalHeight = CGImageGetHeight(image);
|
||||||
|
CGFloat maxWidth = 1024.0f;
|
||||||
|
CGFloat maxHeight = 768.0f;
|
||||||
|
CGFloat ratio = 1.0f;
|
||||||
|
if (originalWidth > maxWidth || originalHeight > maxHeight) {
|
||||||
|
CGFloat ratioW = maxWidth / originalWidth;
|
||||||
|
CGFloat ratioH = maxHeight / originalHeight;
|
||||||
|
ratio = ratioW < ratioH ? ratioW : ratioH;
|
||||||
|
}
|
||||||
|
size_t newWidth = (size_t)(originalWidth * ratio);
|
||||||
|
size_t newHeight = (size_t)(originalHeight * ratio);
|
||||||
|
if (newWidth < 1) newWidth = 1;
|
||||||
|
if (newHeight < 1) newHeight = 1;
|
||||||
|
|
||||||
|
// Create bitmap context
|
||||||
|
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
||||||
|
CGContextRef context = CGBitmapContextCreate(NULL,
|
||||||
|
newWidth,
|
||||||
|
newHeight,
|
||||||
|
8,
|
||||||
|
newWidth * 4,
|
||||||
|
colorSpace,
|
||||||
|
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
|
||||||
|
CGColorSpaceRelease(colorSpace);
|
||||||
|
if (!context) {
|
||||||
CGImageRelease(image);
|
CGImageRelease(image);
|
||||||
|
*outLen = 0;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw image into context to resize
|
||||||
|
CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
|
||||||
|
CGContextDrawImage(context, CGRectMake(0, 0, newWidth, newHeight), image);
|
||||||
|
CGImageRelease(image);
|
||||||
|
|
||||||
|
// Get resized image
|
||||||
|
CGImageRef resizedImage = CGBitmapContextCreateImage(context);
|
||||||
|
CGContextRelease(context);
|
||||||
|
if (!resizedImage) {
|
||||||
|
*outLen = 0;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithCGImage:resizedImage];
|
||||||
|
CGImageRelease(resizedImage);
|
||||||
|
|
||||||
if (!rep) {
|
if (!rep) {
|
||||||
*outLen = 0;
|
*outLen = 0;
|
||||||
|
|||||||
@@ -6,11 +6,35 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"image"
|
||||||
"image/jpeg"
|
"image/jpeg"
|
||||||
|
|
||||||
"github.com/kbinani/screenshot"
|
"github.com/kbinani/screenshot"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// resizeRGBA resizes a *image.RGBA image using Nearest-Neighbor scaling for maximum performance.
|
||||||
|
func resizeRGBA(src *image.RGBA, width, height int) *image.RGBA {
|
||||||
|
dst := image.NewRGBA(image.Rect(0, 0, width, height))
|
||||||
|
srcBounds := src.Bounds()
|
||||||
|
dx := srcBounds.Dx()
|
||||||
|
dy := srcBounds.Dy()
|
||||||
|
minX := srcBounds.Min.X
|
||||||
|
minY := srcBounds.Min.Y
|
||||||
|
|
||||||
|
for y := 0; y < height; y++ {
|
||||||
|
srcY := minY + (y * dy) / height
|
||||||
|
for x := 0; x < width; x++ {
|
||||||
|
srcX := minX + (x * dx) / width
|
||||||
|
|
||||||
|
srcOffset := src.PixOffset(srcX, srcY)
|
||||||
|
dstOffset := dst.PixOffset(x, y)
|
||||||
|
|
||||||
|
copy(dst.Pix[dstOffset:dstOffset+4], src.Pix[srcOffset:srcOffset+4])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
// Non-darwin: dùng kbinani/screenshot
|
||||||
func CaptureScreen() (string, error) {
|
func CaptureScreen() (string, error) {
|
||||||
@@ -26,9 +50,36 @@ func CaptureScreen() (string, error) {
|
|||||||
return "", fmt.Errorf("failed to capture screen: %w", err)
|
return "", fmt.Errorf("failed to capture screen: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tối ưu hóa kích thước ảnh giống dự án Raia:
|
||||||
|
// Giới hạn chiều rộng tối đa là 1024px và chiều cao tối đa là 768px để giảm dung lượng mạng.
|
||||||
|
dx := bounds.Dx()
|
||||||
|
dy := bounds.Dy()
|
||||||
|
|
||||||
|
maxWidth := 1024
|
||||||
|
maxHeight := 768
|
||||||
|
|
||||||
|
newWidth := dx
|
||||||
|
newHeight := dy
|
||||||
|
|
||||||
|
if dx > maxWidth || dy > maxHeight {
|
||||||
|
ratioWidth := float64(maxWidth) / float64(dx)
|
||||||
|
ratioHeight := float64(maxHeight) / float64(dy)
|
||||||
|
ratio := ratioWidth
|
||||||
|
if ratioHeight < ratioWidth {
|
||||||
|
ratio = ratioHeight
|
||||||
|
}
|
||||||
|
newWidth = int(float64(dx) * ratio)
|
||||||
|
newHeight = int(float64(dy) * ratio)
|
||||||
|
}
|
||||||
|
|
||||||
|
var finalImg image.Image = img
|
||||||
|
if newWidth != dx || newHeight != dy {
|
||||||
|
finalImg = resizeRGBA(img, newWidth, newHeight)
|
||||||
|
}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
// Nén chất lượng JPEG khoảng 50% để truyền tải mượt mà qua mạng
|
// Nén chất lượng JPEG khoảng 50% để truyền tải mượt mà qua mạng
|
||||||
err = jpeg.Encode(&buf, img, &jpeg.Options{Quality: 50})
|
err = jpeg.Encode(&buf, finalImg, &jpeg.Options{Quality: 50})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to encode jpeg: %w", err)
|
return "", fmt.Errorf("failed to encode jpeg: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func main() {
|
|||||||
|
|
||||||
// Create application with options
|
// Create application with options
|
||||||
err := wails.Run(&options.App{
|
err := wails.Run(&options.App{
|
||||||
Title: "Simple Care v1.1 — Rikkei Education",
|
Title: "Simple Care v1.2 — Rikkei Education",
|
||||||
Width: 1024,
|
Width: 1024,
|
||||||
Height: 768,
|
Height: 768,
|
||||||
Menu: appMenu,
|
Menu: appMenu,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://wails.io/schemas/config.v2.json",
|
"$schema": "https://wails.io/schemas/config.v2.json",
|
||||||
"name": "Simple Care by Rikkei Edu",
|
"name": "Simple Care by Rikkei Edu",
|
||||||
"outputfilename": "simple_care_v1.1",
|
"outputfilename": "simple_care_v1.2",
|
||||||
"frontend:install": "npm install",
|
"frontend:install": "npm install",
|
||||||
"frontend:build": "npm run build",
|
"frontend:build": "npm run build",
|
||||||
"frontend:dev:watcher": "npm run dev",
|
"frontend:dev:watcher": "npm run dev",
|
||||||
|
|||||||
Reference in New Issue
Block a user