이것저것
원격제어 프로그램 만들기 - 1: 화면공유, 윈도우 본문
이번에 만들것은 서버에서 클라이언트로 화면공유를 시키는 부분이다.
먼저 서버다.
소켓서버를 열고 접속을 기다린다.
아무곳에서나 접속을 한다면, 그쪽으로 화면공유를 하는 함수를 실행한다.
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #서버 생성
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('', 9900))
server_socket.listen()
try:
while True:
client_socket, addr = server_socket.accept() #연결 기다리기
th_send = threading.Thread(target=send, args = (client_socket,addr))
th_send.start() #send함수 실행
except:
server_socket.close();
화면공유할 데이터를 보내는 함수다.
정확히는 스크린샷을 찍어서 보내는 함수인데, 그냥 보내니 속도가 느려서 크기를 줄여야 했다.
1차로 이미지 크기 자체를 줄이고, 2차로 zlib를 사용해 압축한다.
크기를 먼저 보내고 데이터를 전송한다.
screen_size = 0.7 #스크린샷 사이즈
def send(client_socket, addr): #전송함수
global screen_size
print('connect: ', addr)
time.sleep(1) #연결 직후 1초 대기
try:
while(True):
image = pyautogui.screenshot() #스크린샷 촬영
image = image.resize(( int( image.size[0]*(screen_size) ), int( image.size[1]*(screen_size) ) )) #크기조정
data = image.tobytes() #바이트화
data = zlib.compress(data) #압축
length = len(data)
client_socket.sendall(length.to_bytes(4, byteorder="little")) #데이터 크기 전송
client_socket.sendall(data) #데이터 전송
time.sleep(0.01) #딜레이
except:
print("except: " , addr)
finally:
client_socket.close()
이렇게 서버부분은 끝이다. 아직까지는 서버쪽 코드가 간단한 편이다.
이제 클라이언트 부분으로 가자.
이쪽은 gui때문에 코드가 길어진다.
전역변수를 선언한다
화면대기.png는 아직 이미지를 불러오지 않았을때 띄우는 이미지이다.
HOST = '127.0.0.1' #접속ip
PORT = 9900 #접속포트
cursur_xy = [0,0] #커서 좌표
rel_cusur_ratio = [0,0] #커서 위치 비율
screen_size = 0.7 #스크린샷 크기
screen_width = 1920 #스크린샷 가로길이
screen_height = 1080 #스크린샷 세로길이
img_data = Image.open( '화면대기.png' ) #초기이미지 설정
rec_count = 0 #통신횟수
서버에 접속하고, 데이터를 받는 함수를 호출하고, 윈도우 창을 만든다.
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect((HOST, PORT)) #접속
th_receive = threading.Thread(target=receive, args = (client_socket, HOST)) #받기함수 쓰레드
th_receive.start()
app = QtWidgets.QApplication(sys.argv) #윈도우 창
window = character(size=1, on_top=False)
window.run()
sys.exit(app.exec_())
데이터를 받아 이미지로 변환하는 함수이다.
크기가 커서 recv()로 한번에 받아지지 않는데, https://kldp.org/node/46190 이걸 참고하여 해결했다.
해결방법은 다 받아질 때까지 여러번 받는 것이다.
받은 데이터의 압축을 풀고, 이미지로 변수에 저장한다.
def receive(client_socket, addr): #데이터 받기 함수
global img_change, img_data, rec_count, screen_size, screen_width, screen_height
while (True):
start = time.process_time() #시작시간 기록
try:
data = client_socket.recv(4) #데이터 길이를 먼저 받음
length = int.from_bytes(data, "little")
buf = b''
step = length
a=0
while True: #데이터가 전부 받아질 때까지 반복
a += 1
data = client_socket.recv(step)
buf += data
if len(buf) == length:
break
elif len(buf) < length:
step = length - len(buf)
data = zlib.decompress(buf) #압축풀기
img_data = Image.frombytes('RGB', (int(screen_width*screen_size), int(screen_height*screen_size)), data) #이미지 저장
rec_count += 1
except Exception as ex:
if (ex == "Error -3 while decompressing data: invalid code lengths set"): #압축해제 실패 에러
print("connection fail : " , addr)
break
else: #다른 에러는 소켓 관련으로 간주
print("screenshot loading fail")
finally:
end = time.process_time() #끝 시간 기록
print(str(length) + " : " + str(a) + " : " + str(end - start)) #소요시간 출력
gui 윈도우는 전에 만들었던 데스크톱 펫의 코드를 조금 가져왔다
ui를 만들고, flag를 설정하는 함수다
class character(QtWidgets.QMainWindow):
def __init__(self, size=1.0, on_top=False):
super(character, self).__init__()
self.opacity=1 #투명도
self.name = "원격제어" #창 이름
self.size = size #크기
self.on_top = on_top #항상 위에 있을지
self.run_watch = 0 #실행타이머
self.setupUi()
self.show()
def setupUi(self):
global screen_size, screen_width, screen_height
self.centralWidget = QtWidgets.QWidget(self)
self.setCentralWidget(self.centralWidget)
self.setflag(opacity=1)
self.setWindowTitle(self.name) #윈도우 제목 지정
self.label = QtWidgets.QLabel(self.centralWidget)
self.width = int(screen_width*screen_size)
self.height = int(screen_height*screen_size)
self.setGeometry(0, 0, self.width, self.height)
self.imagemanager_pil()
def setflag(self, Tool=False, FWH=False, opacity=1): #플래그 및 투명도 지정함수
if (Tool): self.setWindowFlags(self.windowFlags() | QtCore.Qt.Tool) #작업표시줄에 아이콘이 표시되지 않음
else: self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.Tool)
if (FWH): self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint) #윈도우 틀과 타이틀바 제거
else: self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.FramelessWindowHint)
if (self.on_top): self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) #윈도우가 항상 맨 위에 있음
else: self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowStaysOnTopHint)
self.setWindowOpacity(opacity)
화면공유는 0.1초마다 새로고침한다.
def run(self): #행동함수
self.run_timer = QtCore.QTimer(self)
self.run_timer.timeout.connect(self.__runCore) #0.01초마다 self.__runCore 호출
self.run_timer.start(10)
def __runCore(self):
self.imagemanager_pil()
self.run_watch += 0.01
self.run_watch = round(self.run_watch, 2)
마우스 휘를 돌렸을때 확대/축소를 하게 만든다.
이때 커서의 위치와 상대위치 %를 저장해서, 확대했을때에도 동일한 부분을 볼 수 있게 한다.
def wheelEvent(self, event): #마우스휠 확대
global cursur_xy, rel_cusur_ratio
cursur_xy = [event.pos().x(), event.pos().y()] #커서 위치 저장
rel_cusur_ratio = [(event.pos().x()-self.label.x())/self.label.width(), (event.pos().y()-self.label.y())/self.label.height()] #커서 상대위치% 저장
print (rel_cusur_ratio)
if (event.angleDelta().y() < 0 and not self.size-0.15 <= 0.15): #크기변수 수정
print(self.size)
self.size -= 0.15
elif (event.angleDelta().y() > 0 and not self.size+0.15 >= 5):
self.size += 0.15
마우스를 드래그하면 이미지가 이동하게 한다
def mousePressEvent(self, event):
global cursur_xy, rel_cusur_ratio
cursur_xy = [event.pos().x(), event.pos().y()]
rel_cusur_ratio = [(event.pos().x()-self.label.x())/self.label.width(), (event.pos().y()-self.label.y())/self.label.height()]
def mouseMoveEvent(self, event):
global cursur_xy, rel_cusur_ratio
self.label.move(self.label.x() + (event.x() - cursur_xy[0]), self.label.y() + (event.y() - cursur_xy[1]))
cursur_xy = [event.x(), event.y()]
rel_cusur_ratio = [(event.pos().x()-self.label.x())/self.label.width(), (event.pos().y()-self.label.y())/self.label.height()]
이미지를 변경하는 부분이다.
pixmap에 이미지를 적용하고, 크기를 변경한다.
현재 마우스의 상대좌표 %를 사용하여 위치를 이동한다.
def imagemanager_pil(self): #이미지 변경
global img_data, cursur_xy, rel_cusur_ratio
self.img = ImageQt(img_data).copy()
self.pixmap = QtGui.QPixmap.fromImage(self.img) #pixmap 생성
self.pixmap = self.pixmap.scaled(int(self.pixmap.width()*self.size), int(self.pixmap.height()*self.size)) #사이즈 변경
self.label.setPixmap(self.pixmap) #적용
self.label.resize(self.pixmap.width(), self.pixmap.height()) #라벨 크기 변경
self.label.move(cursur_xy[0] - rel_cusur_ratio[0]*self.label.width(), cursur_xy[1] - rel_cusur_ratio[1]*self.label.height()) #이동
이제 클라이언트쪽 코드도 끝났다.
그렇게 완성한 코드는 다음과 같다.
server.py
import socket, threading
import pyautogui
import time
import zlib
screen_size = 0.7 #스크린샷 사이즈
def send(client_socket, addr): #전송함수
global screen_size
print('connect: ', addr)
time.sleep(1) #연결 직후 1초 대기
try:
while(True):
image = pyautogui.screenshot() #스크린샷 촬영
image = image.resize(( int( image.size[0]*(screen_size) ), int( image.size[1]*(screen_size) ) )) #크기조정
data = image.tobytes() #바이트화
data = zlib.compress(data) #압축
length = len(data)
client_socket.sendall(length.to_bytes(4, byteorder="little")) #데이터 크기 전송
client_socket.sendall(data) #데이터 전송
time.sleep(0.01) #딜레이
except:
print("except: " , addr)
finally:
client_socket.close()
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #서버 생성
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('', 9900))
server_socket.listen()
try:
while True:
client_socket, addr = server_socket.accept() #연결 기다리기
th_send = threading.Thread(target=send, args = (client_socket,addr))
th_send.start() #send함수 실행
except:
server_socket.close()
client.py
import sys
import time
from PIL import Image
import socket
import zlib
import threading
from PyQt5 import QtCore, QtWidgets, QtGui
from PIL.ImageQt import ImageQt
from PyQt5.QtGui import QMovie
HOST = '127.0.0.1' #접속ip
PORT = 9900 #접속포트
cursur_xy = [0,0] #커서 좌표
rel_cusur_ratio = [0,0] #커서 위치 비율
screen_size = 0.7 #스크린샷 크기
screen_width = 1920 #스크린샷 가로길이
screen_height = 1080 #스크린샷 세로길이
img_data = Image.open( '화면대기.png' ) #초기이미지 설정
rec_count = 0 #통신횟수
class character(QtWidgets.QMainWindow):
def __init__(self, size=1.0, on_top=False):
super(character, self).__init__()
self.opacity=1 #투명도
self.name = "원격제어" #창 이름
self.size = size #크기
self.on_top = on_top #항상 위에 있을지
self.run_watch = 0 #실행타이머
self.setupUi()
self.show()
def imagemanager_pil(self): #이미지 변경
global img_data, cursur_xy, rel_cusur_ratio
self.img = ImageQt(img_data).copy()
self.pixmap = QtGui.QPixmap.fromImage(self.img) #pixmap 생성
self.pixmap = self.pixmap.scaled(int(self.pixmap.width()*self.size), int(self.pixmap.height()*self.size)) #사이즈 변경
self.label.setPixmap(self.pixmap) #적용
self.label.resize(self.pixmap.width(), self.pixmap.height()) #라벨 크기 변경
self.label.move(cursur_xy[0] - rel_cusur_ratio[0]*self.label.width(), cursur_xy[1] - rel_cusur_ratio[1]*self.label.height()) #이동
def setupUi(self):
global screen_size, screen_width, screen_height
self.centralWidget = QtWidgets.QWidget(self)
self.setCentralWidget(self.centralWidget)
self.setflag(opacity=1)
self.setWindowTitle(self.name) #윈도우 제목 지정
self.label = QtWidgets.QLabel(self.centralWidget)
self.width = int(screen_width*screen_size)
self.height = int(screen_height*screen_size)
self.setGeometry(0, 0, self.width, self.height)
self.imagemanager_pil()
def setflag(self, Tool=False, FWH=False, opacity=1): #플래그 및 투명도 지정함수
if (Tool): self.setWindowFlags(self.windowFlags() | QtCore.Qt.Tool) #작업표시줄에 아이콘이 표시되지 않음
else: self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.Tool)
if (FWH): self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint) #윈도우 틀과 타이틀바 제거
else: self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.FramelessWindowHint)
if (self.on_top): self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) #윈도우가 항상 맨 위에 있음
else: self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowStaysOnTopHint)
self.setWindowOpacity(opacity)
def resizeEvent(self, event):
self.width = self.centralWidget.frameGeometry().width()
self.height = self.centralWidget.frameGeometry().height()
def wheelEvent(self, event): #마우스휠 확대
global cursur_xy, rel_cusur_ratio
cursur_xy = [event.pos().x(), event.pos().y()] #커서 위치 저장
rel_cusur_ratio = [(event.pos().x()-self.label.x())/self.label.width(), (event.pos().y()-self.label.y())/self.label.height()] #커서 상대위치% 저장
print (rel_cusur_ratio)
if (event.angleDelta().y() < 0 and not self.size-0.15 <= 0.15): #크기변수 수정
print(self.size)
self.size -= 0.15
elif (event.angleDelta().y() > 0 and not self.size+0.15 >= 5):
self.size += 0.15
def mousePressEvent(self, event):
global cursur_xy, rel_cusur_ratio
cursur_xy = [event.pos().x(), event.pos().y()]
rel_cusur_ratio = [(event.pos().x()-self.label.x())/self.label.width(), (event.pos().y()-self.label.y())/self.label.height()]
def mouseMoveEvent(self, event):
global cursur_xy, rel_cusur_ratio
self.label.move(self.label.x() + (event.x() - cursur_xy[0]), self.label.y() + (event.y() - cursur_xy[1]))
cursur_xy = [event.x(), event.y()]
rel_cusur_ratio = [(event.pos().x()-self.label.x())/self.label.width(), (event.pos().y()-self.label.y())/self.label.height()]
def run(self): #행동함수
self.run_timer = QtCore.QTimer(self)
self.run_timer.timeout.connect(self.__runCore) #0.01초마다 self.__runCore 호출
self.run_timer.start(10)
def __runCore(self):
self.imagemanager_pil()
self.run_watch += 0.01
self.run_watch = round(self.run_watch, 2)
def receive(client_socket, addr): #데이터 받기 함수
global img_change, img_data, rec_count, screen_size, screen_width, screen_height
while (True):
start = time.process_time() #시작시간 기록
try:
data = client_socket.recv(4) #데이터 길이를 먼저 받음
length = int.from_bytes(data, "little")
buf = b''
step = length
a=0
while True: #데이터가 전부 받아질 때까지 반복
a += 1
data = client_socket.recv(step)
buf += data
if len(buf) == length:
break
elif len(buf) < length:
step = length - len(buf)
data = zlib.decompress(buf) #압축풀기
img_data = Image.frombytes('RGB', (int(screen_width*screen_size), int(screen_height*screen_size)), data) #이미지 저장
rec_count += 1
except Exception as ex:
if (ex == "Error -3 while decompressing data: invalid code lengths set"): #압축해제 실패 에러
print("connection fail : " , addr)
break
else: #다른 에러는 소켓 관련으로 간주
print("screenshot loading fail")
finally:
end = time.process_time() #끝 시간 기록
print(str(length) + " : " + str(a) + " : " + str(end - start)) #소요시간 출력
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect((HOST, PORT)) #접속
th_receive = threading.Thread(target=receive, args = (client_socket, HOST)) #받기함수 쓰레드
th_receive.start()
app = QtWidgets.QApplication(sys.argv) #윈도우 창
window = character(size=1, on_top=False)
window.run()
sys.exit(app.exec_())
실행영상이다. 마우스로 드래그했고, 마우스 휠을 사용해 확대/축소하였다.
참고로 화면을 공유하는 서브컴은 랜선으로, 공유받는 메인컴은 wifi로 통신하고 있다.
https://www.youtube.com/watch?v=KCy9A-InPhI
'뻘짓 > 원격 제어 프로그램 만들기' 카테고리의 다른 글
원격제어 프로그램 만들기 - 3: 키보드 입력 (0) | 2022.02.19 |
---|---|
원격제어 프로그램 만들기 - 2: 마우스 이벤트 (1) | 2022.02.04 |
원격제어 프로그램 만들기 - 0 (0) | 2022.01.29 |