Skip to content

Commit

Permalink
Merge pull request #1 from ryuuzera/feat-system-control
Browse files Browse the repository at this point in the history
Feat system control
  • Loading branch information
ryuuzera authored Sep 29, 2024
2 parents ded80da + 7c37c77 commit 1159a27
Show file tree
Hide file tree
Showing 16 changed files with 518 additions and 115 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@

*start.js

/server/prisma/zorii.db
*.db

/releases
6 changes: 3 additions & 3 deletions client/src/components/gameList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const Skeletons = () => {
return (
<>
{Array.from({ length: 17 }).map((item, index) => (
<Skeleton key={index * Math.floor(Math.random() * 10 - 1)} className='h-[255px] w-[155px] rounded-none' />
<Skeleton key={index * Math.floor(Math.random() * 100 - 1)} className='h-[255px] w-[155px] rounded-none' />
))}
</>
);
Expand Down Expand Up @@ -92,9 +92,9 @@ export default function GameList({ games }: GameListProps) {
) : (
validGames
?.sort((a, b) => a.name.localeCompare(b.name))
.map((item: SteamGame) => (
.map((item: SteamGame, index) => (
<div
key={item.appid}
key={item.appid + (index + item.name)}
className='flex flex-col h-[230px] w-[155px] p-[3px] hover:p-0 transition-all delay-100 shadow-lg shadow-indigo-500/10 border-none'
onClick={() => handleGameSelect(item)}>
<Image loading='lazy' width={300} height={450} quality={50} alt={item.name} src={item.images.portrait} />
Expand Down
120 changes: 120 additions & 0 deletions client/src/components/system-controls/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
'use client';
import { useSocket } from '@/hook/socket-connection';
import { Fullscreen, Volume1, Volume2, VolumeX } from 'lucide-react';
import { useEffect, useState } from 'react';
import { Button } from '../ui/button';

export default function SystemControls() {
const { socket } = useSocket();
const [lastPosition, setLastPosition] = useState({ x: 0, y: 0 });
const [isDragging, setIsDragging] = useState(false);

useEffect(() => {
socket.on('lastpos', (data) => {
setLastPosition(data);
});
}, []);

const handleMouseDown = (e: any) => {
setIsDragging(true);
setLastPosition({ x: e.clientX, y: e.clientY });
};

const handleMouseMove = (e: any) => {
if (!isDragging) return;

var bounds = e.target.getBoundingClientRect();
var deltaX = e.clientX - bounds.left;
var deltaY = e.clientY - bounds.top;

socket.emit('mousepos', { x: deltaX, y: deltaY });

setLastPosition({ x: e.clientX, y: e.clientY });
};

const handleMouseUp = () => {
setIsDragging(false);
};

const handleTouchStart = (e: any) => {
const touch = e.touches[0];
setIsDragging(true);
setLastPosition({ x: touch.clientX, y: touch.clientY });
};

const handleTouchMove = (e: any) => {
if (!isDragging) return;

const touch = e.touches[0];

const deltaX = touch.clientX - lastPosition.x;
const deltaY = touch.clientY - lastPosition.y;

socket.emit('mousepos', { x: deltaX, y: deltaY });

setLastPosition({ x: touch.clientX, y: touch.clientY });
};

const handleTouchEnd = (e: any) => {
const touch = e.changedTouches[0];

socket.emit('mousestop', { x: touch.clientX, y: touch.clientY });

setIsDragging(false);
};

const sendMessage = (message: string) => {
socket.emit(message);
};

return (
<>
<div className='flex flex-row h-[260px] w-full'>
<div
className='h-full w-[80%] border-[1px] rounded-md flex flex-row-reverse ml-3'
onMouseDown={handleMouseDown}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
onTouchCancel={handleTouchEnd}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
/>
<div className='h-full w-32 flex flex-col p-1 gap-1'>
<Button
className='h-full border-[1px] rounded-md bg-transparent text-white'
onClick={() => sendMessage('left-click')}>
L
</Button>
<Button
className='h-full border-[1px] rounded-md bg-transparent text-white'
onClick={() => sendMessage('right-click')}>
R
</Button>
</div>

<div className='h-full w-full flex flex-row items-center justify-center'>
<Button
className='h-24 w-24 border-2 m-2 bg-transparent text-white'
onClick={() => sendMessage('volumeDown')}>
<Volume1 />
</Button>
<Button className='h-24 w-24 border-2 m-2 bg-transparent text-white' onClick={() => sendMessage('volumeUp')}>
<Volume2 />
</Button>
<Button
className='h-24 w-24 border-2 m-2 bg-transparent text-white'
onClick={() => sendMessage('volumeMute')}>
<VolumeX />
</Button>
<Button
className='h-24 w-24 border-2 m-2 bg-transparent text-white'
onClick={() => sendMessage('printscreen')}>
<Fullscreen />
</Button>
</div>
</div>
</>
);
}
1 change: 0 additions & 1 deletion client/src/components/yt-music-player/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ interface YTMusicPlayerProps {
sendCommand: (command: string, data?: any) => Promise<void>;
}
export function YTMusicPlayer({ playerState, sendCommand }: YTMusicPlayerProps) {
console.log(JSON.stringify(playerState, null, 2));
const [musicProgress, setProgress] = useState<number[]>([playerState?.player?.videoProgress ?? 0]);
const glassEffect = 'bg-black rounded-md bg-clip-padding backdrop-filter backdrop-blur-lg bg-opacity-85 border';

Expand Down
4 changes: 2 additions & 2 deletions client/src/components/yt-music-playlist/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ export function YTMusicPlaylist({ playerState, sendCommand }: YTMusicPlaylistPro
{index == 0 && <CommandSeparator />}
<CommandItem
key={song.videoId + index}
className={`h-20 ${selected ? 'bg-accent text-accent-foreground' : ''} cursor-pointer`}>
className={`h-18 ${selected ? 'bg-accent text-accent-foreground' : ''} cursor-pointer`}>
<div
className='flex flex-row w-full items-center'
onClick={() => {
sendCommand('playQueueIndex', index.toString());
}}>
<img src={song.thumbnails[0].url} className='w-[65px] mr-3' />
<img src={song.thumbnails[0].url} className='w-[42px] mr-3' />
<div className='flex flex-col ml-3 gap-2'>
<span className='font-bold text-[1.13rem]'>{song.title}</span>
<span className='text-[1rem]'>{song.author}</span>
Expand Down
1 change: 0 additions & 1 deletion client/src/lib/http-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ type dataType = 'steam' | 'hardwareinfo' | 'steam/recent';

export async function fetchData<T>(type: dataType) {
try {
console.log('fetchData', type);
const result = await fetch(`http://${process.env.NEXT_PUBLIC_HOST}:${process.env.NEXT_PUBLIC_PORT}/api/${type}`, {
cache: 'no-cache',
});
Expand Down
7 changes: 5 additions & 2 deletions client/src/page-content/control-center.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client';
import { Shortcuts } from '@/components/shortcuts';
import SystemControls from '@/components/system-controls';
import { YTMusicPlayer } from '@/components/yt-music-player';
import { YTMusicPlaylist } from '@/components/yt-music-playlist';
import { pageTransition } from '@/lib/transitions';
Expand All @@ -12,6 +13,7 @@ import { io } from 'socket.io-client';
interface ControlCenterProps {
playerStateData?: YMusicState;
}

export function ControlCenter({ playerStateData }: ControlCenterProps) {
const socket = io(`${ytMusicURL}realtime`, {
autoConnect: false,
Expand Down Expand Up @@ -69,7 +71,7 @@ export function ControlCenter({ playerStateData }: ControlCenterProps) {
return (
<>
<motion.div {...pageTransition}>
<div className='relative flex flex-col gap-3 w-screen max-w-7xl p-2'>
<div className='relative flex flex-col gap-3 h-[calc(100vh-55px)] w-screen max-w-7xl p-2 mt-2'>
{playerState?.player && (
<div className='flex flex-row gap-4 w-full'>
<YTMusicPlayer sendCommand={sendCommand} playerState={playerState} />
Expand All @@ -78,8 +80,9 @@ export function ControlCenter({ playerStateData }: ControlCenterProps) {
</div>
</div>
)}
<Shortcuts shortcuts={shortcuts} />
<SystemControls />
</div>
<Shortcuts shortcuts={shortcuts} />
</motion.div>
</>
);
Expand Down
2 changes: 1 addition & 1 deletion client/src/socket/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { io } from 'socket.io-client';

export const socket = io('http://192.168.0.109:3001', {
export const socket = io(`http://${process.env.NEXT_PUBLIC_HOST}:3001`, {
autoConnect: false,
});
4 changes: 3 additions & 1 deletion server/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,6 @@ dist
.yarn/install-state.gz
.pnp.*

prisma/zorii.db
prisma/*.db

config.ini
3 changes: 3 additions & 0 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@
"license": "MIT",
"dependencies": {
"@prisma/client": "^5.16.1",
"@types/ini": "^4.1.1",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"ffi-napi": "^4.0.3",
"ini": "^5.0.0",
"node-gyp": "^8.4.1",
"ps-list": "^8.1.1",
"robotjs": "^0.6.0",
"socket-io": "^1.0.0",
"socket.io": "^4.7.5",
"sqlite3": "^5.1.7",
Expand Down
Binary file removed server/prisma/zorii.db
Binary file not shown.
61 changes: 45 additions & 16 deletions server/src/application/services/steam.services.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,54 @@
import { exec } from 'child_process';
import { error } from 'console';
import fs from 'fs';
import ini from 'ini';
import path from 'path';
import vdf from 'vdf';
import RecentGameRepository from '../../domain/repository/recentgame.repository';
import { startPresentMon } from './presentmon.services';
import { Socket } from 'socket.io';
import { DefaultEventsMap } from 'socket.io/dist/typed-events';

function steamExists(steamDir: string): boolean {
return fs.existsSync(path.join(steamDir, 'steam.exe'));
}

export function getSteamPath() {
const possibleDirs = [
path.join(process.env['ProgramFiles(x86)'] as string, 'Steam'),
path.join(process.env['ProgramFiles'] as string, 'Steam'),
path.join(process.env.LOCALAPPDATA as string, 'Steam'),
path.join('C:', 'Steam'),
];

const configFilePath = './config.ini';

if (fs.existsSync(configFilePath)) {
const config = ini.parse(fs.readFileSync(configFilePath, 'utf-8'));

if (config.steam && config.steam.path) {
const steamPathFromIni = config.steam.path;

if (steamExists(steamPathFromIni)) {
return steamPathFromIni;
}
}
}

let steamPath = possibleDirs.find(steamExists);

if (steamPath) {
const config = { steam: { path: steamPath } };
fs.writeFileSync('./config.ini', ini.stringify(config));

return steamPath;
}

throw new Error(
'Steam installation path not found. Please set the correct Steam installation path in the config.ini file.'
);
}

function getSteamLibraryFolders() {
const steamPath = path.join(process.env['ProgramFiles(x86)'] as string, 'Steam');
const steamPath = getSteamPath();
const libraryFoldersPath = path.join(steamPath, 'steamapps', 'libraryfolders.vdf');

let libraryFoldersContent: string;
Expand Down Expand Up @@ -115,17 +154,6 @@ export function listInstalledGames() {
return installedGames;
}

function isProcessRunning(processName: string) {
return new Promise((resolve, reject) => {
exec(`tasklist`, (err, stdout, stderr) => {
if (err) {
return reject(`Error executing tasklist: ${stderr}`);
}

resolve(stdout.toLowerCase().includes(processName.toLowerCase()));
});
});
}
function getRunningProcesses(): Promise<any[]> {
return new Promise((resolve, reject) => {
exec('tasklist', (error, stdout) => {
Expand All @@ -136,8 +164,8 @@ function getRunningProcesses(): Promise<any[]> {
.split('\n')
.slice(3)
.map((line) => {
const name = line.substring(0, 25).split('.')[0].trim() + '.exe';
const memoryUsage = line.substring(64).trim();
const name = line.substring(0, 25).split('.')[0].trim() + '.exe';
const memoryUsage = line.substring(64).trim();

const memoryInMB = parseInt(memoryUsage.replace(/[^\d]/g, ''), 10) / 1024 || 0;

Expand All @@ -152,6 +180,7 @@ function getRunningProcesses(): Promise<any[]> {
});
});
}

let processInterval;
export async function runSteamGame(game: { appid: any; name: any }, socket: any) {
clearInterval(processInterval);
Expand Down
Loading

0 comments on commit 1159a27

Please sign in to comment.