Compare commits

..

1 Commits

Author SHA1 Message Date
Brandon Tuttle 4a6e02ae7d
Merge ccfd63b84f into 15768ee0ab 2025-02-11 09:16:21 -05:00
7 changed files with 20 additions and 101 deletions

View File

@ -7,14 +7,13 @@ import (
"fmt"
"net/http"
"net/url"
"time"
"github.com/coder/websocket/wsjson"
"time"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/coder/websocket"
"github.com/gin-gonic/gin"
"github.com/coder/websocket"
)
type CloudRegisterRequest struct {
@ -193,11 +192,7 @@ func handleSessionRequest(ctx context.Context, c *websocket.Conn, req WebRTCSess
return fmt.Errorf("google identity mismatch")
}
session, err := newSession(SessionConfig{
ICEServers: req.ICEServers,
LocalIP: req.IP,
IsCloud: true,
})
session, err := newSession()
if err != nil {
_ = wsjson.Write(context.Background(), c, gin.H{"error": err})
return err

View File

@ -10,7 +10,6 @@
"dev": "vite dev --mode=development",
"build": "npm run build:prod",
"build:device": "tsc && vite build --mode=device --emptyOutDir",
"dev:device": "vite dev --mode=device",
"build:prod": "tsc && vite build --mode=production",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
},

View File

@ -4,7 +4,6 @@ import {
useMountMediaStore,
useUiStore,
useSettingsStore,
useVideoStore,
} from "@/hooks/stores";
import { MdOutlineContentPasteGo } from "react-icons/md";
import Container from "@components/Container";
@ -34,7 +33,6 @@ export default function Actionbar({
state => state.remoteVirtualMediaState,
);
const developerMode = useSettingsStore(state => state.developerMode);
const hdmiState = useVideoStore(state => state.hdmiState);
// This is the only way to get a reliable state change for the popover
// at time of writing this there is no mount, or unmount event for the popover
@ -249,7 +247,6 @@ export default function Actionbar({
size="XS"
theme="light"
text="Fullscreen"
disabled={hdmiState !== 'ready'}
LeadingIcon={LuMaximize}
onClick={() => requestFullscreen()}
/>

View File

@ -30,8 +30,6 @@ export default function WebRTCVideo() {
const {
setClientSize: setVideoClientSize,
setSize: setVideoSize,
width: videoWidth,
height: videoHeight,
clientWidth: videoClientWidth,
clientHeight: videoClientHeight,
} = useVideoStore();
@ -104,43 +102,20 @@ export default function WebRTCVideo() {
const mouseMoveHandler = useCallback(
(e: MouseEvent) => {
if (!videoClientWidth || !videoClientHeight) return;
// Get the aspect ratios of the video element and the video stream
const videoElementAspectRatio = videoClientWidth / videoClientHeight;
const videoStreamAspectRatio = videoWidth / videoHeight;
const { buttons } = e;
// Calculate the effective video display area
let effectiveWidth = videoClientWidth;
let effectiveHeight = videoClientHeight;
let offsetX = 0;
let offsetY = 0;
// Clamp mouse position within the video boundaries
const currMouseX = Math.min(Math.max(1, e.offsetX), videoClientWidth);
const currMouseY = Math.min(Math.max(1, e.offsetY), videoClientHeight);
if (videoElementAspectRatio > videoStreamAspectRatio) {
// Pillarboxing: black bars on the left and right
effectiveWidth = videoClientHeight * videoStreamAspectRatio;
offsetX = (videoClientWidth - effectiveWidth) / 2;
} else if (videoElementAspectRatio < videoStreamAspectRatio) {
// Letterboxing: black bars on the top and bottom
effectiveHeight = videoClientWidth / videoStreamAspectRatio;
offsetY = (videoClientHeight - effectiveHeight) / 2;
}
// Clamp mouse position within the effective video boundaries
const clampedX = Math.min(Math.max(offsetX, e.offsetX), offsetX + effectiveWidth);
const clampedY = Math.min(Math.max(offsetY, e.offsetY), offsetY + effectiveHeight);
// Map clamped mouse position to the video stream's coordinate system
const relativeX = (clampedX - offsetX) / effectiveWidth;
const relativeY = (clampedY - offsetY) / effectiveHeight;
// Convert to HID absolute coordinate system (0-32767 range)
const x = Math.round(relativeX * 32767);
const y = Math.round(relativeY * 32767);
// Normalize mouse position to 0-32767 range (HID absolute coordinate system)
const x = Math.round((currMouseX / videoClientWidth) * 32767);
const y = Math.round((currMouseY / videoClientHeight) * 32767);
// Send mouse movement
const { buttons } = e;
sendMouseMovement(x, y, buttons);
},
[sendMouseMovement, videoClientHeight, videoClientWidth, videoWidth, videoHeight],
[sendMouseMovement, videoClientHeight, videoClientWidth],
);
const mouseWheelHandler = useCallback(

View File

@ -2,31 +2,13 @@ import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import tsconfigPaths from "vite-tsconfig-paths";
declare const process: {
env: {
JETKVM_PROXY_URL: string;
};
};
export default defineConfig(({ mode, command }) => {
export default defineConfig(({ mode }) => {
const isCloud = mode === "production";
const onDevice = mode === "device";
const { JETKVM_PROXY_URL } = process.env;
return {
plugins: [tsconfigPaths(), react()],
build: { outDir: isCloud ? "dist" : "../static" },
server: {
host: "0.0.0.0",
proxy: JETKVM_PROXY_URL ? {
'/me': JETKVM_PROXY_URL,
'/device': JETKVM_PROXY_URL,
'/webrtc': JETKVM_PROXY_URL,
'/auth': JETKVM_PROXY_URL,
'/storage': JETKVM_PROXY_URL,
'/cloud': JETKVM_PROXY_URL,
} : undefined
},
base: onDevice && command === 'build' ? "/static" : "/",
server: { host: "0.0.0.0" },
base: onDevice ? "/static" : "/",
};
});

4
web.go
View File

@ -19,8 +19,6 @@ var staticFiles embed.FS
type WebRTCSessionRequest struct {
Sd string `json:"sd"`
OidcGoogle string `json:"OidcGoogle,omitempty"`
IP string `json:"ip,omitempty"`
ICEServers []string `json:"iceServers,omitempty"`
}
type SetPasswordRequest struct {
@ -118,7 +116,7 @@ func handleWebRTCSession(c *gin.Context) {
return
}
session, err := newSession(SessionConfig{})
session, err := newSession()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err})
return

View File

@ -4,7 +4,6 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"net"
"strings"
"github.com/pion/webrtc/v4"
@ -20,12 +19,6 @@ type Session struct {
shouldUmountVirtualMedia bool
}
type SessionConfig struct {
ICEServers []string
LocalIP string
IsCloud bool
}
func (s *Session) ExchangeOffer(offerStr string) (string, error) {
b, err := base64.StdEncoding.DecodeString(offerStr)
if err != nil {
@ -68,29 +61,9 @@ func (s *Session) ExchangeOffer(offerStr string) (string, error) {
return base64.StdEncoding.EncodeToString(localDescription), nil
}
func newSession(config SessionConfig) (*Session, error) {
webrtcSettingEngine := webrtc.SettingEngine{}
iceServer := webrtc.ICEServer{}
if config.IsCloud {
if config.ICEServers == nil {
fmt.Printf("ICE Servers not provided by cloud")
} else {
iceServer.URLs = config.ICEServers
fmt.Printf("Using ICE Servers provided by cloud: %v\n", iceServer.URLs)
}
if config.LocalIP == "" || net.ParseIP(config.LocalIP) == nil {
fmt.Printf("Local IP address %v not provided or invalid, won't set NAT1To1IPs\n", config.LocalIP)
} else {
webrtcSettingEngine.SetNAT1To1IPs([]string{config.LocalIP}, webrtc.ICECandidateTypeSrflx)
fmt.Printf("Setting NAT1To1IPs to %s\n", config.LocalIP)
}
}
api := webrtc.NewAPI(webrtc.WithSettingEngine(webrtcSettingEngine))
peerConnection, err := api.NewPeerConnection(webrtc.Configuration{
ICEServers: []webrtc.ICEServer{iceServer},
func newSession() (*Session, error) {
peerConnection, err := webrtc.NewPeerConnection(webrtc.Configuration{
ICEServers: []webrtc.ICEServer{{}},
})
if err != nil {
return nil, err