Ozgurce

Çalışkan
Katılım
6 Mart 2026
Mesajlar
7
Beğeniler
7
Uzun zamandır telefondan bilgisayarın sesini, şarkılarını ya da açmak istediğim siteleri açmak için uygulama falan kullanıyordum. Bugün biraz da zaman geçsin diye uğraşırken kendi çapımda bir şeyler oluşturdum. Bu bir ekrana bağlanıp Windows'u yönetme uygulaması değil. Çünkü amacım daha basit PC TV'ye bağlıyken ya da bir iş yaparken müzik açıkken falan şarkı değiştirmeye ses açıp kısmaya ya da yatağa geçtim ama PC hala açıksa kapatmaya falan yarayan bir şey yapmaktı. Şarkı değiştirme ya da arama gelince uzaktaysam durdurma vs. gibi şeyler yapmak için.

Gereklilikler:
  • Python 3.12 (sonraki versiyonlar windsk ile çalışmadığı için mecbur bu sürümdeyiz) kurulumunu anlatmayacağım o apayrı ve daha uzun bir konu olur.
  • Eğer localde yani aynı ağda olmadan mobil veriyle falan da bağlanmak isterseniz "tailscale" uygulaması. Uygulamayı Windows başladığında başlayacak şekilde ayarlayın.
  • Sıcaklıkları görmek için librehardwaremonitor. Bu uygulamayı kurup üstten "web server" kısmında run demelisiniz. Bu uygulamayı da Windows başlangıcında çalışacak şekilde ayarlayın.
Python'u kurduktan sonra C:\'de PC-Control diye bir klasör oluşturuyoruz. İçine kontrol.py diye bir dosya atıyoruz. Dosyanın içeriği şu şekilde olacak:

Python:
import os
import subprocess
import ctypes
import traceback
import json
import time
import asyncio
import re
import urllib.request
import base64

import psutil
import pyautogui
from aiohttp import web

WINSDK_AVAILABLE = True
try:
    from winsdk.windows.media.control import GlobalSystemMediaTransportControlsSessionManager
    from winsdk.windows.storage.streams import DataReader
except Exception:
    WINSDK_AVAILABLE = False

PYGETWINDOW_AVAILABLE = True
try:
    import pygetwindow as gw
except Exception:
    PYGETWINDOW_AVAILABLE = False

PORT = 5000
BASE_DIR = r"C:\pc-control"
LOG_FILE = os.path.join(BASE_DIR, "kontrol_log.txt")
ERR_FILE = os.path.join(BASE_DIR, "kontrol_error.txt")
LHM_URL = "http://127.0.0.1:8085/data.json"

_LAST_MEDIA_ERROR_AT = 0.0
_LAST_LHM_ERROR_AT = 0.0
_ERROR_COOLDOWN_SECONDS = 60.0

pyautogui.FAILSAFE = False
pyautogui.PAUSE = 0


def log(text: str) -> None:
    try:
        with open(LOG_FILE, "a", encoding="utf-8") as f:
            f.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} | {text}\n")
    except Exception:
        pass


def log_error(text: str) -> None:
    try:
        with open(ERR_FILE, "a", encoding="utf-8") as f:
            f.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} | {text}\n")
    except Exception:
        pass


def throttled_media_error(text: str) -> None:
    global _LAST_MEDIA_ERROR_AT
    now = time.time()
    if now - _LAST_MEDIA_ERROR_AT >= _ERROR_COOLDOWN_SECONDS:
        _LAST_MEDIA_ERROR_AT = now
        log_error(text)


def throttled_lhm_error(text: str) -> None:
    global _LAST_LHM_ERROR_AT
    now = time.time()
    if now - _LAST_LHM_ERROR_AT >= _ERROR_COOLDOWN_SECONDS:
        _LAST_LHM_ERROR_AT = now
        log_error(text)


def open_url_in_chrome(url: str) -> bool:
    candidates = [
        r"C:\Program Files\Google\Chrome\Application\chrome.exe",
        r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
        os.path.expandvars(r"%LocalAppData%\Google\Chrome\Application\chrome.exe"),
    ]

    for chrome_path in candidates:
        try:
            if os.path.exists(chrome_path):
                subprocess.Popen([chrome_path, url])
                return True
        except Exception as e:
            log_error(f"Chrome açma hatası ({chrome_path}): {e}")

    try:
        subprocess.Popen(['cmd', '/c', 'start', '', 'chrome', url], shell=False)
        return True
    except Exception as e:
        log_error(f"Start chrome hatası: {e}")

    return False


def open_spotify_store_app() -> bool:
    methods = [
        lambda: os.startfile("spotify:"),
        lambda: subprocess.Popen(['cmd', '/c', 'start', '', 'spotify:'], shell=False),
        lambda: subprocess.Popen(['explorer.exe', 'spotify:'], shell=False),
    ]

    for method in methods:
        try:
            method()
            log("Spotify Store uygulaması açma komutu gönderildi")
            return True
        except Exception as e:
            log_error(f"Spotify açma hatası: {e}")

    return False


WM_APPCOMMAND = 0x319
APPCOMMAND_VOLUME_MUTE = 0x80000
APPCOMMAND_VOLUME_DOWN = 0x90000
APPCOMMAND_VOLUME_UP = 0xA0000


def send_app_command(cmd: int) -> None:
    hwnd = ctypes.windll.user32.GetForegroundWindow()
    if hwnd == 0:
        hwnd = 0xFFFF
    ctypes.windll.user32.SendMessageW(hwnd, WM_APPCOMMAND, hwnd, cmd)


def toggle_mute() -> None:
    send_app_command(APPCOMMAND_VOLUME_MUTE)


def volume_up() -> None:
    send_app_command(APPCOMMAND_VOLUME_UP)


def volume_down() -> None:
    send_app_command(APPCOMMAND_VOLUME_DOWN)


def media_play_pause() -> None:
    pyautogui.press("playpause")


def media_next() -> None:
    pyautogui.press("nexttrack")


def media_prev() -> None:
    pyautogui.press("prevtrack")


def press_key(key_name: str) -> None:
    pyautogui.press(key_name)


def left_click() -> None:
    pyautogui.click()


def right_click() -> None:
    pyautogui.click(button="right")


def get_media_info_from_windows_titles() -> dict:
    if not PYGETWINDOW_AVAILABLE:
        return {"title": "Medya bilgisi alınamadı", "artist": "", "art_url": None}

    try:
        titles = [t.strip() for t in gw.getAllTitles() if t and t.strip()]
        spotify_titles = [
    t for t in titles
    if "spotify" in t.lower()
    and "rainmeter" not in t.lower()
    and ".ini" not in t.lower()
]

        meaningful = []
        for t in spotify_titles:
            if t.lower() != "spotify":
                meaningful.append(t)

        if meaningful:
            chosen = meaningful[0]
            if " - " in chosen:
                left, right = chosen.split(" - ", 1)
                return {
                    "title": left.strip() or chosen,
                    "artist": right.strip(),
                    "art_url": None
                }
            return {"title": chosen, "artist": "", "art_url": None}

        if spotify_titles:
            return {
                "title": "Spotify açık",
                "artist": "Parça bilgisi pencere başlığında yok",
                "art_url": None
            }

        return {"title": "Aktif medya oturumu bulunamadı", "artist": "", "art_url": None}
    except Exception as e:
        throttled_media_error(f"Pencere başlığı medya fallback hatası: {e}")
        return {"title": "Medya bilgisi alınamadı", "artist": "", "art_url": None}


async def get_thumbnail_data_uri(thumbnail_ref):
    if thumbnail_ref is None:
        return None
    try:
        stream = await thumbnail_ref.open_read_async()
        size = stream.size
        if size == 0:
            return None

        reader = DataReader(stream)
        await reader.load_async(size)
        buffer = reader.read_buffer(size)
        bytes_data = bytes(buffer)
        b64 = base64.b64encode(bytes_data).decode("ascii")
        content_type = getattr(stream, "content_type", None) or "image/jpeg"
        return f"data:{content_type};base64,{b64}"
    except Exception as e:
        throttled_media_error(f"Kapak görseli hatası: {e}")
        return None


async def _get_media_info_async():
    if not WINSDK_AVAILABLE:
        return get_media_info_from_windows_titles()

    try:
        manager = await GlobalSystemMediaTransportControlsSessionManager.request_async()
        session = manager.get_current_session()

        if session is None:
            return get_media_info_from_windows_titles()

        props = await session.try_get_media_properties_async()
        title = getattr(props, "title", "") or ""
        artist = getattr(props, "artist", "") or ""
        thumbnail = getattr(props, "thumbnail", None)

        art_url = await get_thumbnail_data_uri(thumbnail)

        if not title and not artist:
            return get_media_info_from_windows_titles()

        return {
            "title": title or "Parça bilgisi yok",
            "artist": artist or "",
            "art_url": art_url
        }

    except Exception as e:
        throttled_media_error(f"Medya bilgisi hatası: {e}\n{traceback.format_exc()}")
        return get_media_info_from_windows_titles()


def get_media_info():
    try:
        return asyncio.run(_get_media_info_async())
    except RuntimeError:
        try:
            loop = asyncio.new_event_loop()
            try:
                return loop.run_until_complete(_get_media_info_async())
            finally:
                loop.close()
        except Exception as e:
            throttled_media_error(f"asyncio media hatası: {e}")
            return get_media_info_from_windows_titles()
    except Exception as e:
        throttled_media_error(f"asyncio media hatası: {e}")
        return get_media_info_from_windows_titles()


def get_gpu_info() -> dict:
    try:
        result = subprocess.run(
            [
                "nvidia-smi",
                "--query-gpu=utilization.gpu,temperature.gpu",
                "--format=csv,noheader,nounits"
            ],
            capture_output=True,
            text=True,
            timeout=2,
            creationflags=subprocess.CREATE_NO_WINDOW
        )

        if result.returncode != 0 or not result.stdout.strip():
            return {"gpu_util": None, "gpu_temp": None}

        line = result.stdout.strip().splitlines()[0]
        parts = [x.strip() for x in line.split(",")]
        return {
            "gpu_util": float(parts[0]),
            "gpu_temp": float(parts[1])
        }
    except Exception as e:
        log_error(f"GPU info hatası: {e}")
        return {"gpu_util": None, "gpu_temp": None}


def parse_temp_value(value):
    if value is None:
        return None
    if isinstance(value, (int, float)):
        return float(value)

    match = re.search(r"(-?\d+(?:\.\d+)?)", str(value))
    if match:
        try:
            return float(match.group(1))
        except Exception:
            return None
    return None


def collect_temperature_sensors(node, results):
    if isinstance(node, dict):
        text = str(node.get("Text", "")).strip()
        value = node.get("Value", "")
        image_url = str(node.get("ImageURL", "")).lower()
        parsed = parse_temp_value(value)

        if parsed is not None:
            text_l = text.lower()
            if (
                "temperature" in image_url
                or "°" in str(value)
                or "cpu" in text_l
                or "ccd" in text_l
                or "tdie" in text_l
                or "package" in text_l
                or "core max" in text_l
                or "dram" in text_l
                or "dimm" in text_l
                or "memory" in text_l
                or "ram" in text_l
            ):
                results.append({
                    "text": text,
                    "value": parsed,
                    "raw": str(value)
                })

        for child in node.get("Children", []):
            collect_temperature_sensors(child, results)

    elif isinstance(node, list):
        for item in node:
            collect_temperature_sensors(item, results)


def get_all_temperature_sensors():
    try:
        with urllib.request.urlopen(LHM_URL, timeout=1.0) as response:
            raw = response.read().decode("utf-8", errors="ignore")
            data = json.loads(raw)

        results = []
        collect_temperature_sensors(data, results)
        return results
    except Exception as e:
        log_error(f"Tüm sıcaklık sensörleri okunamadı: {e}")
        return []


def score_cpu_sensor(name: str) -> int:
    n = name.lower().strip()
    score = 0

    # EN ÖNCE Tctl/Tdie
    if "tctl/tdie" in n:
        score += 300

    if "ccd1 (tdie)" in n:
        score += 200

    if "tdie" in n:
        score += 180

    if "cpu die" in n:
        score += 150

    if "cpu package" in n:
        score += 120

    if n == "package":
        score += 100

    if "core max" in n:
        score += 90

    # alakasız sensörleri ele
    if "gpu" in n:
        score -= 100
    if "system" in n:
        score -= 100
    if "motherboard" in n:
        score -= 100
    if "pch" in n:
        score -= 100
    if "vrm" in n:
        score -= 100
    if "dimm" in n or "dram" in n or "memory" in n or "ram" in n:
        score -= 100

    return score


def score_ram_sensor(name: str) -> int:
    n = name.lower().strip()
    score = 0

    if "dram temperature" in n:
        score += 120
    if "memory temperature" in n:
        score += 115
    if "ram temperature" in n:
        score += 110
    if "dimm temp" in n:
        score += 105
    if "dimm" in n:
        score += 95
    if "dram" in n:
        score += 90
    if "memory" in n:
        score += 85
    if "ram" in n:
        score += 80

    if "cpu" in n or "gpu" in n or "package" in n or "ccd" in n or "tdie" in n:
        score -= 100

    return score


def choose_best_sensor(sensors, scorer):
    best = None
    best_score = -10**9

    for sensor in sensors:
        score = scorer(sensor["text"])
        if score > best_score:
            best_score = score
            best = sensor

    if best_score <= 0:
        return None
    return best


def get_lhm_sensor_info() -> dict:
    result = {
        "cpu_temp": None,
        "ram_temp": None,
        "cpu_sensor_name": None,
        "ram_sensor_name": None
    }

    try:
        sensors = get_all_temperature_sensors()

        cpu_sensor = choose_best_sensor(sensors, score_cpu_sensor)
        ram_sensor = choose_best_sensor(sensors, score_ram_sensor)

        if cpu_sensor:
            result["cpu_temp"] = cpu_sensor["value"]
            result["cpu_sensor_name"] = cpu_sensor["text"]

        if ram_sensor:
            result["ram_temp"] = ram_sensor["value"]
            result["ram_sensor_name"] = ram_sensor["text"]

        return result
    except Exception as e:
        throttled_lhm_error(f"LHM okuma hatası: {e}")
        return result


def get_system_info() -> dict:
    try:
        cpu_percent = round(psutil.cpu_percent(interval=0.08), 1)
        ram = psutil.virtual_memory()
        media = get_media_info()
        gpu = get_gpu_info()
        lhm = get_lhm_sensor_info()

        return {
            "cpu_temp": lhm["cpu_temp"],
            "cpu_sensor_name": lhm["cpu_sensor_name"],
            "cpu_percent": cpu_percent,
            "gpu_util": gpu["gpu_util"],
            "gpu_temp": gpu["gpu_temp"],
            "ram_percent": round(ram.percent, 1),
            "ram_temp": lhm["ram_temp"],
            "ram_sensor_name": lhm["ram_sensor_name"],
            "media_title": media.get("title", "Bilgi yok"),
            "media_artist": media.get("artist", ""),
            "media_art_url": media.get("art_url", None)
        }
    except Exception as e:
        log_error(f"System info hatası: {e}\n{traceback.format_exc()}")
        return {
            "cpu_temp": None,
            "cpu_sensor_name": None,
            "cpu_percent": None,
            "gpu_util": None,
            "gpu_temp": None,
            "ram_percent": None,
            "ram_temp": None,
            "ram_sensor_name": None,
            "media_title": "Bilgi alınamadı",
            "media_artist": "",
            "media_art_url": None
        }


def run_command(path: str):
    log(f"Komut geldi: {path}")

    if path == "/shutdown":
        os.system("shutdown /s /t 0")
        return True, "Kapatma komutu gönderildi"

    if path == "/restart":
        os.system("shutdown /r /t 0")
        return True, "Yeniden başlatma komutu gönderildi"

    if path == "/sleep":
        os.system("rundll32.exe powrprof.dll,SetSuspendState 0,1,0")
        return True, "Uyku komutu gönderildi"

    if path == "/lock":
        ctypes.windll.user32.LockWorkStation()
        return True, "Kilitleme komutu gönderildi"

    if path == "/spotify":
        ok = open_spotify_store_app()
        return ok, "Spotify açıldı" if ok else "Spotify açılamadı"

    if path == "/shorts":
        ok = open_url_in_chrome("https://www.youtube.com/shorts")
        return ok, "YouTube Shorts Chrome'da açıldı" if ok else "YouTube Shorts açılamadı"

    if path == "/tiktok":
        ok = open_url_in_chrome("https://www.tiktok.com/")
        return ok, "TikTok Chrome'da açıldı" if ok else "TikTok açılamadı"

    if path == "/mute":
        toggle_mute()
        return True, "Ses aç/kapat komutu gönderildi"

    if path == "/volup":
        volume_up()
        return True, "Ses artırıldı"

    if path == "/voldown":
        volume_down()
        return True, "Ses azaltıldı"

    if path == "/playpause":
        media_play_pause()
        return True, "Oynat/duraklat komutu gönderildi"

    if path == "/next":
        media_next()
        return True, "Sonraki şarkı komutu gönderildi"

    if path == "/prev":
        media_prev()
        return True, "Önceki şarkı komutu gönderildi"

    if path == "/up":
        press_key("up")
        return True, "Yukarı tuşu gönderildi"

    if path == "/down":
        press_key("down")
        return True, "Aşağı tuşu gönderildi"

    if path == "/left":
        press_key("left")
        return True, "Sol tuşu gönderildi"

    if path == "/right":
        press_key("right")
        return True, "Sağ tuşu gönderildi"

    if path == "/ok":
        press_key("enter")
        return True, "Enter tuşu gönderildi"

    if path == "/click":
        left_click()
        return True, "Sol tık gönderildi"

    if path == "/rightclick":
        right_click()
        return True, "Sağ tık gönderildi"

    return False, "Bilinmeyen komut"


HTML_PAGE = r"""
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>PC Kontrol</title>
<style>
:root{
    --panel:#131c30;
    --panel2:#18243c;
    --text:#eff6ff;
    --muted:#94a3b8;
    --line:rgba(255,255,255,.08);
    --cyan:#13b5d1;
    --cyan2:#0d7f98;
    --pink:#ff1478;
    --pink2:#d60f64;
    --amber:#ffb400;
    --amber2:#db9b00;
    --green:#1ed760;
    --green2:#16b34d;
    --red:#ef4444;
    --red2:#dc2626;
    --orange:#f97316;
    --orange2:#ea580c;
}
*{
    box-sizing:border-box;
    -webkit-tap-highlight-color:transparent;
}
html, body{
    touch-action:manipulation;
}
button{
    touch-action:manipulation;
}
body{
    margin:0;
    padding:8px;
    color:var(--text);
    font-family:Inter,Segoe UI,Arial,sans-serif;
    background:linear-gradient(180deg,#0a1020,#0d1425 55%,#0c1220);
}
.wrap{max-width:1100px;margin:0 auto;}
.hero,.card{
    background:linear-gradient(180deg,var(--panel),var(--panel2));
    border:1px solid var(--line);
    border-radius:20px;
    box-shadow:0 10px 30px rgba(0,0,0,.25);
}
.hero{padding:12px 14px;margin-bottom:8px;}
.hero h1{margin:0;font-size:22px;}
.grid{
    display:grid;
    grid-template-columns:repeat(12,1fr);
    gap:8px;
}
.card{padding:12px;}
.span4{grid-column:span 4;}
.span6{grid-column:span 6;}
.span12{grid-column:span 12;}
.stat-label{
    color:var(--muted);
    font-size:13px;
    letter-spacing:.12em;
    text-transform:uppercase;
    margin-bottom:6px;
}
.stat-value{
    font-size:24px;
    font-weight:900;
    margin-bottom:6px;
}
.stat-meta{
    color:var(--muted);
    font-size:12px;
    line-height:1.35;
}
.sensor-name{
    color:#7dd3fc;
    font-size:11px;
    margin-top:3px;
    line-height:1.2;
    height:28px;
    overflow:hidden;
    display:-webkit-box;
    -webkit-line-clamp:2;
    -webkit-box-orient:vertical;
    word-break:break-word;
}
.chart-wrap{
    height:62px;
    margin-top:8px;
}
.chart-wrap canvas{
    width:100%;
    height:100%;
    display:block;
    border-radius:12px;
    background:rgba(255,255,255,.03);
}
.section-title{
    margin:0 0 6px 0;
    font-size:16px;
    font-weight:800;
}
.section-sub{
    color:var(--muted);
    font-size:11px;
    margin-bottom:10px;
}
.now-playing{
    display:grid;
    grid-template-columns:64px 1fr;
    gap:10px;
    align-items:center;
}
.cover{
    width:64px;
    height:64px;
    border-radius:14px;
    object-fit:cover;
    background:rgba(255,255,255,.06);
    border:1px solid var(--line);
    display:block;
}
.media-info{
    min-width:0;
}
.song-title{
    font-size:16px;
    font-weight:900;
    margin-bottom:4px;
    line-height:1.25;
    word-break:break-word;
    display:-webkit-box;
    -webkit-line-clamp:2;
    -webkit-box-orient:vertical;
    overflow:hidden;
}
.song-artist{
    font-size:12px;
    color:#d4deec;
    margin-bottom:0;
    word-break:break-word;
    display:-webkit-box;
    -webkit-line-clamp:2;
    -webkit-box-orient:vertical;
    overflow:hidden;
}
.btn-grid-3{
    display:grid;
    grid-template-columns:repeat(3, minmax(0, 1fr));
    gap:7px;
}
.btn-grid-4{
    display:grid;
    grid-template-columns:repeat(4, minmax(0, 1fr));
    gap:7px;
}
.pill{
    width:100%;
    min-height:54px;
    border:none;
    border-radius:14px;
    padding:8px 6px;
    display:flex;
    flex-direction:column;
    align-items:center;
    justify-content:center;
    text-align:center;
    color:#fff;
    font-size:12px;
    font-weight:800;
    line-height:1.1;
    cursor:pointer;
    box-shadow:0 8px 18px rgba(0,0,0,.22);
    transition:
        transform 0.08s ease,
        box-shadow 0.08s ease,
        filter 0.08s ease,
        opacity 0.08s ease;
    gap:4px;
}
.pill-icon{
    font-size:35px;
    line-height:1;
}
.pill:active,
.pill.pressed{
    transform:translateY(2px) scale(.975);
    filter:brightness(.93);
    box-shadow:inset 0 3px 9px rgba(0,0,0,.34);
}
.cyan{background:linear-gradient(180deg,var(--cyan),var(--cyan2));}
.pink{background:linear-gradient(180deg,var(--pink),var(--pink2));}
.amber{background:linear-gradient(180deg,var(--amber),var(--amber2));color:#231800;}
.green{background:linear-gradient(180deg,var(--green),var(--green2));color:#082610;}
.dark{background:linear-gradient(180deg,#22324d,#1a2640);}
.red{background:linear-gradient(180deg,var(--red),var(--red2));}
.orange{background:linear-gradient(180deg,var(--orange),var(--orange2));}
.statusbar{
    margin-top:8px;
    padding:12px 14px;
    border-radius:16px;
    border:1px solid var(--line);
    background:rgba(255,255,255,.04);
    color:#d0dbeb;
    font-size:13px;
}
.touchpad{
    position:relative;
    width:100%;
    height:190px;
    border-radius:18px;
    border:1px solid var(--line);
    background:
        linear-gradient(180deg, rgba(255,255,255,.06), rgba(255,255,255,.03)),
        linear-gradient(180deg,#0f172a,#111827);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.04);
    overflow:hidden;
    touch-action:none;
    user-select:none;
    margin-top:4px;
}
.touchpad::before{
    content:"";
    position:absolute;
    inset:0;
    background:
        linear-gradient(rgba(255,255,255,.035) 1px, transparent 1px),
        linear-gradient(90deg, rgba(255,255,255,.035) 1px, transparent 1px);
    background-size:24px 24px;
    pointer-events:none;
}
.touchpad-label{
    position:absolute;
    left:12px;
    top:10px;
    color:rgba(255,255,255,.55);
    font-size:12px;
    letter-spacing:.08em;
    text-transform:uppercase;
    pointer-events:none;
}
.touchpad-actions{
    display:grid;
    grid-template-columns:1fr 1fr;
    gap:7px;
    margin-top:10px;
}
.dpad-row{
    display:grid;
    grid-template-columns:repeat(5, minmax(0, 1fr));
    gap:7px;
    margin-top:8px;
}
.dpad-btn{
    min-height:50px;
    border:none;
    border-radius:14px;
    background:linear-gradient(180deg,#22324d,#1a2640);
    color:#eff6ff;
    font-size:22px;
    font-weight:900;
    box-shadow:0 8px 18px rgba(0,0,0,.22);
    cursor:pointer;
    transition:
        transform 0.08s ease,
        box-shadow 0.08s ease,
        filter 0.08s ease,
        opacity 0.08s ease;
}
.dpad-btn:active,
.dpad-btn.pressed{
    transform:translateY(2px) scale(.965);
    filter:brightness(.93);
    box-shadow:inset 0 3px 9px rgba(0,0,0,.34);
}
.dpad-ok{
    background:linear-gradient(180deg,var(--cyan),var(--cyan2));
    font-size:14px;
}
@media (max-width:860px){
    .span6{grid-column:span 12;}
}
@media (max-width:560px){
    body{padding:8px;}
    .hero{padding:12px 14px;}
    .hero h1{font-size:22px;}
    .grid{gap:8px;}
    .card{padding:12px;}
    .span6{grid-column:span 12;}
    .btn-grid-3{
        grid-template-columns:repeat(3, minmax(0, 1fr));
        gap:7px;
    }
    .btn-grid-4{
        grid-template-columns:repeat(4, minmax(0, 1fr));
        gap:7px;
    }
    .touchpad-actions{
        grid-template-columns:1fr 1fr;
        gap:7px;
    }
    .dpad-row{
        grid-template-columns:repeat(5, minmax(0, 1fr));
        gap:7px;
    }
}
</style>
</head>
<body>
<div class="wrap">

    <div class="grid">
        <div class="card span4">
            <div class="section-title">CPU</div>
            <div class="stat-value" id="cpuTemp">-</div>
            <div class="stat-meta" id="cpuMeta">Sıcaklık / Kullanım</div>
            <div class="sensor-name" id="cpuSensorName"></div>
            <div class="chart-wrap"><canvas id="cpuChart"></canvas></div>
        </div>

        <div class="card span4">
            <div class="section-title">GPU</div>
            <div class="stat-value" id="gpuTemp">-</div>
            <div class="stat-meta" id="gpuMeta">Sıcaklık / Kullanım</div>
            <div class="sensor-name"></div>
            <div class="chart-wrap"><canvas id="gpuChart"></canvas></div>
        </div>

        <div class="card span4">
            <div class="section-title">RAM</div>
            <div class="stat-value" id="ramTemp">-</div>
            <div class="stat-meta" id="ramMeta">Sıcaklık / Kullanım</div>
            <div class="sensor-name" id="ramSensorName"></div>
            <div class="chart-wrap"><canvas id="ramChart"></canvas></div>
        </div>

        <div class="card span12">
            <div class="now-playing">
                <img id="coverArt" class="cover" alt="" />
                <div class="media-info">
                    <div class="song-title" id="mediaTitle">Parça bilgisi yükleniyor...</div>
                    <div class="song-artist" id="mediaArtist"></div>
                </div>
            </div>
        </div>

        <div class="card span6">
            <div class="btn-grid-3">
                <button class="pill dark pressable" data-path="/playpause"><span class="pill-icon">⏯</span></button>
                <button class="pill dark pressable" data-path="/prev"><span class="pill-icon">⏮</span></button>
                <button class="pill dark pressable" data-path="/next"><span class="pill-icon">⏭</span></button>

                <button class="pill cyan pressable" data-path="/mute"><span class="pill-icon">🔇</span></button>
                <button class="pill cyan pressable" data-path="/voldown"><span class="pill-icon">🔉</span></button>
                <button class="pill cyan pressable" data-path="/volup"><span class="pill-icon">🔊</span></button>

                <button class="pill pressable" data-path="/tiktok"><svg class="pill-icon" viewBox="0 0 24 24" width="40" height="40" fill="#000000">
<path d="M21 8.5a7.5 7.5 0 01-4.5-1.5V15a6 6 0 11-6-6c.3 0 .6 0 .9.1v3.1a3 3 0 10 2.1 2.9V0h3a4.5 4.5 0 004.5 4.5v4z"/>
</svg></button>
                <button class="pill pressable" data-path="/shorts">
                <svg class="pill-icon" viewBox="0 0 24 24" width="40" height="40" fill="#FF0000">
<path d="M23.5 6.2a3 3 0 00-2.1-2.1C19.6 3.5 12 3.5 12 3.5s-7.6 0-9.4.6A3 3 0 00.5 6.2 31.4 31.4 0 000 12a31.4 31.4 0 00.5 5.8 3 3 0 002.1 2.1c1.8.6 9.4.6 9.4.6s7.6 0 9.4-.6a3 3 0 002.1-2.1A31.4 31.4 0 0024 12a31.4 31.4 0 00-.5-5.8zM9.75 15.5v-7l6.5 3.5-6.5 3.5z"/></button>
                <button class="pill black pressable" data-path="/spotify">
                <svg class="pill-icon" viewBox="0 0 24 24" width="40" height="40" fill="#1ED760">
<path d="M12 0C5.37 0 0 5.37 0 12s5.37 12 12 12 12-5.37 12-12S18.63 0 12 0zm5.52 17.34a.75.75 0 01-1.03.25c-2.82-1.72-6.37-2.11-10.55-1.16a.75.75 0 11-.33-1.46c4.54-1.03 8.44-.59 11.68 1.38.36.22.47.68.23.99zm1.47-3.27a.94.94 0 01-1.29.31c-3.23-1.98-8.15-2.56-11.97-1.41a.94.94 0 11-.54-1.8c4.36-1.32 9.78-.67 13.47 1.59.44.27.58.85.33 1.31zm.13-3.41c-3.87-2.3-10.26-2.51-13.96-1.38a1.13 1.13 0 11-.66-2.17c4.25-1.29 11.31-1.04 15.77 1.63a1.13 1.13 0 11-1.15 1.92z"/>
</svg></button>
            </div>
        </div>

        <div class="card span6">
            <div class="btn-grid-4">
                <button class="pill dark pressable" data-path="/sleep"><span class="pill-icon">🌙</span></button>
                <button class="pill dark pressable" data-path="/lock"><span class="pill-icon">🔒︎</span></button>
                <button class="pill green pressable" data-path="/restart" data-confirm="Bilgisayar yeniden başlatılsın mı?"><span class="pill-icon">🔄</span></button>
                <button class="pill red pressable" data-path="/shutdown" data-confirm="Bilgisayar kapatılsın mı?"><span class="pill-icon">⏻</span></button>
            </div>
        </div>

        <div class="card span12">

            <div id="touchpad" class="touchpad">
                <div class="touchpad-label">Fare</div>
            </div>

            <div class="touchpad-actions">
                <button class="pill dark pressable" data-path="/click"><span>Sol Tık</span></button>
                <button class="pill dark pressable" data-path="/rightclick"><span>Sağ Tık</span></button>
            </div>

            <div class="dpad-row">
                <button class="dpad-btn pressable" data-path="/left">←</button>
                <button class="dpad-btn pressable" data-path="/up">↑</button>
                <button class="dpad-btn dpad-ok pressable" data-path="/ok">OK</button>
                <button class="dpad-btn pressable" data-path="/down">↓</button>
                <button class="dpad-btn pressable" data-path="/right">→</button>
            </div>
        </div>
    </div>

    <div class="statusbar" id="status">Hazır</div>
</div>

<script>
const historySize = 24;
const cpuHistory = [];
const gpuHistory = [];
const ramHistory = [];

let mouseSocket = null;
let socketReady = false;
let pendingMoveX = 0;
let pendingMoveY = 0;
let socketFlushTimer = null;

function pushHistory(arr, value){
    arr.push(value ?? 0);
    if(arr.length > historySize) arr.shift();
}

function fmtTemp(v){
    return (v === null || v === undefined) ? "Yok" : v.toFixed(1) + "°C";
}
function fmtPercent(v){
    return (v === null || v === undefined) ? "Yok" : v.toFixed(0) + "%";
}

function drawChart(canvasId, data, lineColor){
    const canvas = document.getElementById(canvasId);
    if(!canvas) return;

    const w = Math.max(10, Math.floor(canvas.clientWidth));
    const h = Math.max(10, Math.floor(canvas.clientHeight));
    const dpr = window.devicePixelRatio || 1;

    canvas.width = Math.floor(w * dpr);
    canvas.height = Math.floor(h * dpr);

    const ctx = canvas.getContext('2d');

    // Her çizimde context'i sıfırla
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // CSS piksel alanında çiz
    ctx.scale(dpr, dpr);

    ctx.strokeStyle = 'rgba(255,255,255,0.08)';
    ctx.lineWidth = 1;

    for(let i = 1; i <= 3; i++){
        const y = (h / 4) * i;
        ctx.beginPath();
        ctx.moveTo(0, y);
        ctx.lineTo(w, y);
        ctx.stroke();
    }

    if(data.length < 2) return;

    ctx.beginPath();
    data.forEach((v, i) => {
        const x = (w / (historySize - 1)) * i;
        const y = h - (Math.max(0, Math.min(100, v)) / 100) * h;
        if(i === 0) ctx.moveTo(x, y);
        else ctx.lineTo(x, y);
    });

    ctx.strokeStyle = lineColor;
    ctx.lineWidth = 2.5;
    ctx.stroke();

    ctx.lineTo(w, h);
    ctx.lineTo(0, h);
    ctx.closePath();
    ctx.fillStyle = lineColor.replace('1)', '0.14)');
    ctx.fill();
}

function setPressed(el, pressed){
    if(!el) return;
    if(pressed) el.classList.add('pressed');
    else el.classList.remove('pressed');
}

function pulseButton(el, ms=120){
    if(!el) return;
    setPressed(el, true);
    setTimeout(() => setPressed(el, false), ms);
}

async function go(path){
    const status = document.getElementById('status');
    status.textContent = "Gönderiliyor: " + path;
    try{
        const response = await fetch(path);
        const text = await response.text();
        status.textContent = text;
        setTimeout(refreshInfo, 350);
    }catch(err){
        status.textContent = "Hata: " + err;
    }
}

function confirmAction(path, message){
    if(confirm(message)){ go(path); }
}

async function refreshInfo(){
    try{
        const response = await fetch('/status');
        const data = await response.json();

        document.getElementById('cpuTemp').textContent = fmtTemp(data.cpu_temp);
        document.getElementById('cpuMeta').textContent =
            fmtTemp(data.cpu_temp) + " / " + fmtPercent(data.cpu_percent);
        document.getElementById('cpuSensorName').textContent =
            data.cpu_sensor_name ? ("Sensör: " + data.cpu_sensor_name) : "";

        document.getElementById('gpuTemp').textContent = fmtTemp(data.gpu_temp);
        document.getElementById('gpuMeta').textContent =
            fmtTemp(data.gpu_temp) + " / " + fmtPercent(data.gpu_util);

        document.getElementById('ramTemp').textContent = fmtTemp(data.ram_temp);
        document.getElementById('ramMeta').textContent =
            fmtTemp(data.ram_temp) + " / " + fmtPercent(data.ram_percent);
        document.getElementById('ramSensorName').textContent =
            data.ram_sensor_name ? ("Sensör: " + data.ram_sensor_name) : "";

        document.getElementById('mediaTitle').textContent = data.media_title || "Parça bilgisi yok";
        document.getElementById('mediaArtist').textContent = data.media_artist || "";

const cover = document.getElementById('coverArt');
const noMedia =
    !data.media_title ||
    data.media_title === "Aktif medya oturumu bulunamadı" ||
    data.media_title === "Medya bilgisi alınamadı" ||
    data.media_title === "Bilgi alınamadı";

if(data.media_art_url && !noMedia){
    cover.src = data.media_art_url;
    cover.style.display = 'block';
} else {
    cover.removeAttribute('src');
    cover.style.display = 'none';
}
const nowPlaying = document.querySelector('.now-playing');
if(data.media_art_url && !noMedia){
    nowPlaying.style.gridTemplateColumns = '64px 1fr';
} else {
    nowPlaying.style.gridTemplateColumns = '1fr';
}

        pushHistory(cpuHistory, data.cpu_percent);
        pushHistory(gpuHistory, data.gpu_util);
        pushHistory(ramHistory, data.ram_percent);

        drawChart('cpuChart', cpuHistory, 'rgba(19,181,209,1)');
        drawChart('gpuChart', gpuHistory, 'rgba(255,20,120,1)');
        drawChart('ramChart', ramHistory, 'rgba(30,215,96,1)');

        document.getElementById('status').textContent =
            "Son güncelleme: " + new Date().toLocaleTimeString('tr-TR');
    }catch(err){
        document.getElementById('status').textContent =
            "Bilgi yenileme hatası: " + err;
    }
}

function setupButtons(){
    const buttons = document.querySelectorAll('.pressable');

    buttons.forEach(btn => {
        btn.addEventListener('pointerdown', () => setPressed(btn, true));
        btn.addEventListener('pointerup', () => setPressed(btn, false));
        btn.addEventListener('pointerleave', () => setPressed(btn, false));
        btn.addEventListener('pointercancel', () => setPressed(btn, false));

        btn.addEventListener('click', () => {
            const path = btn.dataset.path;
            const confirmMessage = btn.dataset.confirm;

            if(path && confirmMessage){
                pulseButton(btn);
                confirmAction(path, confirmMessage);
                return;
            }

            if(path){
                pulseButton(btn);
                go(path);
            }
        });
    });
}

function connectMouseSocket(){
    const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
    const wsUrl = protocol + '//' + location.host + '/ws/mouse';

    mouseSocket = new WebSocket(wsUrl);

    mouseSocket.onopen = () => {
        socketReady = true;
        document.getElementById('status').textContent = 'Mouse WebSocket bağlı';
    };

    mouseSocket.onclose = () => {
        socketReady = false;
        setTimeout(connectMouseSocket, 1200);
    };

    mouseSocket.onerror = () => {
        socketReady = false;
    };

    mouseSocket.onmessage = (event) => {
        try{
            const msg = JSON.parse(event.data);
            if(msg.type === 'mouse-status' && msg.message){
                document.getElementById('status').textContent = msg.message;
            }
        }catch(_){}
    };
}

function flushMouseSocket(){
    socketFlushTimer = null;

    if(!socketReady || !mouseSocket || mouseSocket.readyState !== WebSocket.OPEN){
        pendingMoveX = 0;
        pendingMoveY = 0;
        return;
    }

    const dx = Math.round(pendingMoveX);
    const dy = Math.round(pendingMoveY);

    pendingMoveX = 0;
    pendingMoveY = 0;

    if(dx === 0 && dy === 0) return;

    mouseSocket.send(JSON.stringify({
        type: 'move',
        dx: dx,
        dy: dy
    }));
}

function queueMouseMove(dx, dy){
    pendingMoveX += dx;
    pendingMoveY += dy;

    if(!socketFlushTimer){
        socketFlushTimer = setTimeout(flushMouseSocket, 8);
    }
}

function setupTouchpad(){
    const pad = document.getElementById('touchpad');
    if(!pad) return;

    let active = false;
    let lastX = 0;
    let lastY = 0;
    let tapTimer = null;
    let touchStartX = 0;
    let touchStartY = 0;
    let touchMoved = false;

    const speed = 1.35;

    pad.addEventListener('pointerdown', (e) => {
        active = true;
        lastX = e.clientX;
        lastY = e.clientY;
        touchStartX = e.clientX;
        touchStartY = e.clientY;
        touchMoved = false;

        try{
            pad.setPointerCapture(e.pointerId);
        }catch(_){}
    });

    pad.addEventListener('pointermove', (e) => {
        if(!active) return;

        const rawDx = e.clientX - lastX;
        const rawDy = e.clientY - lastY;

        lastX = e.clientX;
        lastY = e.clientY;

        if(Math.abs(e.clientX - touchStartX) > 8 || Math.abs(e.clientY - touchStartY) > 8){
            touchMoved = true;
        }

        const dx = rawDx * speed;
        const dy = rawDy * speed;

        if(dx !== 0 || dy !== 0){
            queueMouseMove(dx, dy);
        }
    });

    function stopPad(){
        active = false;
        flushMouseSocket();
    }

    pad.addEventListener('pointerup', () => {
        stopPad();

        if(touchMoved) return;

        if(tapTimer){
            clearTimeout(tapTimer);
            tapTimer = null;
            go('/click');
            return;
        }

        tapTimer = setTimeout(() => {
            tapTimer = null;
        }, 220);
    });

    pad.addEventListener('pointercancel', stopPad);
    pad.addEventListener('lostpointercapture', stopPad);
    pad.addEventListener('contextmenu', (e) => e.preventDefault());

    pad.addEventListener('dblclick', (e) => {
        e.preventDefault();
        go('/click');
    });
}

window.addEventListener('load', () => {
    setTimeout(() => {
        drawChart('cpuChart', cpuHistory, 'rgba(19,181,209,1)');
        drawChart('gpuChart', gpuHistory, 'rgba(255,20,120,1)');
        drawChart('ramChart', ramHistory, 'rgba(30,215,96,1)');
    }, 100);
});

window.addEventListener('resize', () => {
    requestAnimationFrame(() => {
        drawChart('cpuChart', cpuHistory, 'rgba(19,181,209,1)');
        drawChart('gpuChart', gpuHistory, 'rgba(255,20,120,1)');
        drawChart('ramChart', ramHistory, 'rgba(30,215,96,1)');
    });
});

setupButtons();
connectMouseSocket();
setupTouchpad();
refreshInfo();
setInterval(refreshInfo, 2000);
</script>
</body>
</html>
"""


async def handle_root(request):
    return web.Response(text=HTML_PAGE, content_type="text/html")


async def handle_status(request):
    data = await asyncio.to_thread(get_system_info)
    return web.json_response(data, dumps=lambda x: json.dumps(x, ensure_ascii=False))


async def handle_temps(request):
    data = await asyncio.to_thread(get_all_temperature_sensors)
    return web.json_response(data, dumps=lambda x: json.dumps(x, ensure_ascii=False))


async def handle_command(request):
    path = request.path
    ok, msg = await asyncio.to_thread(run_command, path)
    if ok:
        return web.Response(text=msg, content_type="text/plain")
    return web.Response(text=f"Komut başarısız oldu: {path} | {msg}", status=400, content_type="text/plain")


async def handle_mouse_ws(request):
    ws = web.WebSocketResponse(heartbeat=20)
    await ws.prepare(request)

    await ws.send_json({"type": "mouse-status", "message": "Mouse WebSocket hazır"})

    try:
        async for msg in ws:
            if msg.type == web.WSMsgType.TEXT:
                try:
                    payload = json.loads(msg.data)
                except Exception:
                    continue

                if payload.get("type") == "move":
                    dx = int(payload.get("dx", 0))
                    dy = int(payload.get("dy", 0))

                    if dx != 0 or dy != 0:
                        def move_mouse():
                            x, y = pyautogui.position()
                            pyautogui.moveTo(x + dx, y + dy, duration=0)

                        await asyncio.to_thread(move_mouse)

                elif payload.get("type") == "click":
                    await asyncio.to_thread(left_click)

                elif payload.get("type") == "rightclick":
                    await asyncio.to_thread(right_click)

            elif msg.type == web.WSMsgType.ERROR:
                log_error(f"WebSocket hata: {ws.exception()}")
    except Exception as e:
        log_error(f"Mouse WebSocket handler hatası: {e}\n{traceback.format_exc()}")
    finally:
        await ws.close()

    return ws


def create_app():
    app = web.Application(client_max_size=1024 * 1024)

    app.router.add_get("/", handle_root)
    app.router.add_get("/status", handle_status)
    app.router.add_get("/temps", handle_temps)
    app.router.add_get("/ws/mouse", handle_mouse_ws)

    for route in [
        "/shutdown", "/restart", "/sleep", "/lock",
        "/spotify", "/shorts", "/tiktok",
        "/mute", "/volup", "/voldown",
        "/playpause", "/next", "/prev",
        "/up", "/down", "/left", "/right", "/ok",
        "/click", "/rightclick"
    ]:
        app.router.add_get(route, handle_command)

    return app


if __name__ == "__main__":
    try:
        os.makedirs(BASE_DIR, exist_ok=True)
        log("WebSocket sürümü script başladı")
        app = create_app()
        web.run_app(app, host="0.0.0.0", port=PORT)
    except Exception as e:
        log_error(f"HATA OLUŞTU: {e}\n{traceback.format_exc()}")

Kod:
Set WshShell = CreateObject("WScript.Shell")
WshShell.Run """C:\Users\Ozgur\AppData\Local\Microsoft\WindowsApps\python.exe"" ""C:\pc-control\kontrol.py""", 0, False
Ekliyoruz. Bu bizim üstteki dosyayı arka planda sessizce çalıştırmamıza yarayacak. Diğer türlü sürekli olarak açık bir komut penceresi görürsünüz.
Sonra bu VBA'yı zamanlanmış görevlere ekliyoruz. (Pythonun kurulu olduğu yeri ve exenin konumunu düzenlemeyi unutmayın). Bulamazsanız Where Python diye komutla sorgulayabilirsiniz.

1773088816821.webp


Bu şekilde olmalı. Ben oturum açılınca diye yaptım açılışı geciktirmesin diye.

Buraya kadar her şey tamamsa kendi bilgisayarınızdan http://127.0.0.1:5000/ ya da aynı ağa bağlı telefonunuzdan pcninipsi: 5000/ diye girerek kontrol paneline ulaşabilirsiniz. Çok fazla Debug yapmadım sağ olsun Claude donup durduğu için optimize etmeye de erindim.
Sonuç şu şekilde bir ekran olacak. Burada aktif çalan medyayı kontrol ediyorsunuz yani YouTube'da çalıyorsa durdur deyince o durur.

Eğer webden kullanmak isterseniz tailscale kullanmanız lazım.
Tailscale PC'niz ve telefonunuza sanal bir VPN içine alır. Yani uygulamayı telefona ve PC'ye kurarsanız modemde port açmaya gerek kalmadan oradan oluşan PC ipsine telefondan erişebilirsiniz. Sol kullandığım için portla falan uğraşmak istemedim açıckası. Çoğu zaman açılmıyor zaten.
Hataları ve logları txt olarak aynı klasöre kaydediyor. Oradan kontrol edebilirsiniz.

Dediğim gibi bitmiş bir şey gibi değil de üzerinde bir şeyler yapılabilecek geliştirmeye açık bir şey olarak düşünebilirsiniz. Kendim için yaptığımdan çok fazla geliştirme beklemeyin lütfen.

Pythonda gerekli derlemeler şu şekilde

Python:
pip install aiohttp psutil pyautogui pygetwindow winsdk

Claude aboneliğim bitmese biraz daha uğraşırdım muhtemelen ama html/css yazmaya gerçekten eriniyorum :D

Panel şu şekilde:

5870960779283074449.webp

5870960779283074448.webp
 
Son düzenleyen: Moderatör:
Güzel yorumlarınız için teşekkür ederim. Aslında amacım ekranın altına yerleştirdiğim Waveshare bir ekrana bu scripti yerleştirip AIDA ya da HWiNFO falan gerek duymadan çok da kaynak tüketmeyen bir panel oluşturmaktı. Telefon ikinci plandaydı, sonra telefona yapınca o ekrana da geçiş yaptım, o da bitti sayılır. Ekranın dokunmatik olması paneldeki butonlara kısayol atayıp işimi kolaylaştırmamı sağladı.
Şöyle bir panel oldu yani.

1773264239621.webp


Reelde böyle duruyor.

1773264303648.webp


Çok fazla gereksinimi var, paylaşacağım bunu da ama ne ekledim, neyi nereden alıyorum biraz karıştı açıkçası. PresentMon'dan FPS alıyoruz, şarkı sözlerini 'https://lrclib.net/apiden çekiyoruz; şarkı çalıyorsa (WebNowPlaying'den alıyor aslında) şarkının sözünü çekiyor ve şarkının o an bulunduğu duruma göre sözleri yazdırıyor. Bir Spotify lyric değildir ama göze takılan bir gecikmesi falan yok. Belki biraz daha modern görünsün diye giriş çıkış efekti eklerim ileride. CPU vs. bilgileri Libre'den alıyorum. Kapat, uyut ya da yeniden başlata basınca soruyor "yapayım mı?" diye, tamam dersen yapıyor. Üstteki şarkı ismi de o an çalınan medyayı gösteriyor, YouTube vs. dahil.
Beni en çok zorlayan şey adam gibi görünen ve çalışan bir ses slider'ı yapmaktı. Şükür güzel görünüyor o da şimdi.
Ne kadar eklenti kullandığımı şuradan görebilirsiniz. O yüzden optimize etmeden paylaşamam.

Python:
import os
import subprocess
import ctypes
import json
import time
import asyncio
import re
import base64
import threading
import uuid
import urllib.parse
import datetime
from collections import deque

import psutil
import pyautogui
from aiohttp import web
import aiohttp

Bu diğer telefon kontrolünden ayrı bir portta çalışıyor, yani ikisini de aynı anda çalıştırabilirsiniz, sorun olmaz. Ona volume slider ekleyemedim daha, işten zaman kaldıkça bakacağım ona da.

Yatay:

1773962063801.webp


Dikey:

1773962082125.webp


Bir gün vakit bulunca bu şekli de paylaşacağım. Ben bunu bilgisayarın yanına aldığım dokunmatik 8.8 inch Waveshare bir ekran için kodladım. Bir tık kaynak harcıyor olabilir, optimize etmeye pek vaktim olmadı. Niyetim Steam Deck benzeri (bence çok da gerekli bir meret değil ama) bir panel oluşturmaktı. AIDA Infopanel falan denedim ama AIDA fareyi sapıttırıyordu, Infopanel de %5 falan CPU kullanıyor. Telefondan kontrol bonusu oldu aslında yani. Toparlayınca Python kodlarını, HTML/CSS/JS kodlarını, imajları falan atarım buraya.
Şu an böyle görünüyor

1773962444574.webp


Olay da şu; ilgilenenler için bu paneli sadece 2. monitörde açılacak WebView bir pencereye dönüştürüyoruz. Pythonw yani windowed ile çalıştırıyoruz, böylelikle başlat çubuğunda bir şey olmuyor. Her zaman üstte falan ayarlıyoruz. Böylelikle oyun oynarken falan FPS takip edilebiliyor. Tek sorun %1 Low takibi çünkü PresentMon'dan asla anlamlı bir veri alamıyorum onun için. Visualizer eklemeyi de denedim ama Python'da onu çalıştırmak işkence oldu ve aşırı RAM tüketti çünkü Edge WebView penceresinde çalıştırıyoruz.
 
Son düzenleyen: Moderatör: