Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

이것저것

원격제어 프로그램 만들기 - 3: 키보드 입력 본문

뻘짓/원격 제어 프로그램 만들기

원격제어 프로그램 만들기 - 3: 키보드 입력

정육면체 2022. 2. 19. 02:10

<전편>

 

이번에는 키보드 입력을 구현해보자.

 

pyautogui로 서버의 키보드를 제어한다.

def keyboard_control(first, second, control_type):
	btn1 = str(first)
	btn2 = str(second)
	if (control_type == "press"): pyautogui.keyDown(btn1)
	elif (control_type == "release"): pyautogui.keyUp(btn1)

 

클라이언트로부터 키가 눌렸다는 통신을 받으면 위 함수를 호출한다.

elif (data[0] == "key_press"): keyboard_control(data[1], data[2], "press")
elif (data[0] == "key_release"): keyboard_control(data[1], data[2], "release")

 

서버쪽은 이게 끝이다.

이제 클라이언트로 가보자. 클라이언트는 키보드 입력을 받아 서버로 전송한다.

 

pyqt 자체에 키보드 관련 이벤트가 있었다.

이벤트는 pyqt를 사용하지만, pyautogui로 제어를 하기 때문에 이벤트값을 변환해줘야 한다.

def key_name(key):			#qt 키 이벤트 이름 반환함수
	if(key == QtCore.Qt.Key_A): return("a")
	elif(key == QtCore.Qt.Key_B): return("b")
	elif(key == QtCore.Qt.Key_C): return("c")
	elif(key == QtCore.Qt.Key_D): return("d")
	elif(key == QtCore.Qt.Key_E): return("e")
	elif(key == QtCore.Qt.Key_F): return("f")
	elif(key == QtCore.Qt.Key_G): return("g")
	elif(key == QtCore.Qt.Key_H): return("h")
	elif(key == QtCore.Qt.Key_I): return("i")
	elif(key == QtCore.Qt.Key_J): return("j")
	elif(key == QtCore.Qt.Key_K): return("k")
	elif(key == QtCore.Qt.Key_L): return("l")
	elif(key == QtCore.Qt.Key_M): return("m")
	elif(key == QtCore.Qt.Key_N): return("n")
	elif(key == QtCore.Qt.Key_O): return("o")
	elif(key == QtCore.Qt.Key_P): return("p")
	elif(key == QtCore.Qt.Key_Q): return("q")
	elif(key == QtCore.Qt.Key_R): return("r")
	elif(key == QtCore.Qt.Key_S): return("s")
	elif(key == QtCore.Qt.Key_T): return("t")
	elif(key == QtCore.Qt.Key_U): return("u")
	elif(key == QtCore.Qt.Key_V): return("v")
	elif(key == QtCore.Qt.Key_W): return("w")
	elif(key == QtCore.Qt.Key_X): return("x")
	elif(key == QtCore.Qt.Key_Y): return("y")
	elif(key == QtCore.Qt.Key_Z): return("z")
	elif(key == QtCore.Qt.Key_0): return("0")
	elif(key == QtCore.Qt.Key_1): return("1")
	elif(key == QtCore.Qt.Key_2): return("2")
	elif(key == QtCore.Qt.Key_3): return("3")
	elif(key == QtCore.Qt.Key_4): return("4")
	elif(key == QtCore.Qt.Key_5): return("5")
	elif(key == QtCore.Qt.Key_6): return("6")
	elif(key == QtCore.Qt.Key_7): return("7")
	elif(key == QtCore.Qt.Key_8): return("8")
	elif(key == QtCore.Qt.Key_9): return("9")
	elif(key == QtCore.Qt.Key_Space): return("space")
	elif(key == QtCore.Qt.Key_Backspace): return("backspace")
	elif(key == QtCore.Qt.Key_Delete): return("delete")
	elif(key == QtCore.Qt.Key_Return): return("enter")
	elif(key == QtCore.Qt.Key_Shift): return("shift")
	elif(key == QtCore.Qt.Key_Control): return("ctrl")
	elif(key == QtCore.Qt.Key_Alt): return("alt")
	elif(key == QtCore.Qt.Key_Up): return("up")
	elif(key == QtCore.Qt.Key_Down): return("down")
	elif(key == QtCore.Qt.Key_Left): return("left")
	elif(key == QtCore.Qt.Key_Right): return("right")
	elif(key == QtCore.Qt.Key_Semicolon): return(";")
	elif(key == QtCore.Qt.Key_Greater): return(">")
	elif(key == QtCore.Qt.Key_Equal): return("=")
	elif(key == QtCore.Qt.Key_Plus): return("+")
	elif(key == QtCore.Qt.Key_Minus): return("-")
	elif(key == QtCore.Qt.Key_Question): return("?")
	elif(key == QtCore.Qt.Key_Tab): return("tab")

등록된(?) pyqt의 이벤트로 pyautogui에서의 키이름을 반환하는 함수다.

알파벳, 숫자, 기타 자주쓰는 키들을 등록했다. 나머지는 귀찮으니 패스

키 하나하나 elif로 등록하는 이상한 코드와 가독성은 둘째치고, 이랬을때 가장 큰 문제는 한글을 쓰지 못한다는 것이다.

qt의 키보드 이벤트는 한영키를 비롯한 각종 한글을 지원하지 않는다.

정확히는, 눌렀다고는 인식하는데, 그게 어떤 키인지를 구별하지 못한다

Key_hangul라는 키를 찾았는데, 테스트하니 다른것인지 작동을 하지 않았다.

너무 복잡해지니까 영어로 만족하고 한글은 나중으로 미루자. 

 

큐를 추가하고, 마우스이벤트때의 코드를 공유하며 서버로 전송한다.

def keyPressEvent(self, event):
	send_queue.put(send_type("key_press", key_name(event.key()), 0, self.run_watch))	#큐 추가
def keyReleaseEvent(self, event):
	send_queue.put(send_type("key_release", key_name(event.key()), 0, self.run_watch))	#큐 추가

 

 

지금까지의 전체 코드는 다음과 같다.

server.py

import socket, threading
import pyautogui
import time
import zlib
screen_size = 0.7		#스크린샷 사이즈
scroll = 0.02

def send(client_socket, addr):		#전송함수
	global screen_size
	print('connect: ', addr)
	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)				#데이터 전송
	except Exception as ex:
		return 0
	finally:
		client_socket.close()

def receive(client_socket, addr):		#데이터 받기 함수
	length = 0
	a = 0
	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 = buf.decode('utf-8').split(":")
			if (data[0] == "mouse_move"): mouse_control(data[1], data[2], "move")
			elif (data[0] == "mouse_left_down"): mouse_control(data[1], data[2], "down", "left")
			elif (data[0] == "mouse_left_up"): mouse_control(data[1], data[2], "up", "left")
			elif (data[0] == "mouse_right_down"): mouse_control(data[1], data[2], "down", "right")
			elif (data[0] == "mouse_right_up"): mouse_control(data[1], data[2], "up", "right")
			elif (data[0] == "mouse_wheel"): mouse_control(data[1], data[2], "wheel")
			elif (data[0] == "key_press"): keyboard_control(data[1], data[2], "press")
			elif (data[0] == "key_release"): keyboard_control(data[1], data[2], "release")

		except Exception as ex:
			print(ex)
			client_socket.close()
		finally:
			end = time.process_time()		#끝 시간 기록
			print("receive -> " + str(length) + " : " + str(a) + " : " + str(end - start))		#소요시간 출력

def mouse_control(first, second, control_type, button="none"):		#마우스 제어함수
	global scroll
	tx = int(first)
	ty = int(second)

	if (control_type == "move"): pyautogui.moveTo(int(tx), int(ty))
	elif (control_type == "down"): pyautogui.mouseDown(int(tx), int(ty), button)
	elif (control_type == "up"): pyautogui.mouseUp(int(tx), int(ty), button)
	elif (control_type == "wheel"): pyautogui.scroll(int(tx*scroll))

def keyboard_control(first, second, control_type):				#키보드 제어함수
	btn1 = str(first)
	btn2 = str(second)
	if (control_type == "press"): pyautogui.keyDown(btn1)
	elif (control_type == "release"): pyautogui.keyUp(btn1)




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_receive = threading.Thread(target=receive, args = (client_socket,addr))
		th_send.start()			#send함수 실행
		th_receive.start()			#receive함수 실행
except:
	server_socket.close()

 

client.py

import sys
import time
from PIL import Image
import keyboard
import pynput
import json
import socket
import zlib
import threading
import pyautogui
from queue import Queue
from PyQt5 import QtCore, QtWidgets, QtGui
from PIL.ImageQt import ImageQt
#HOST = '127.0.0.1'			#접속ip
HOST = ''			#접속ip
PORT = 9900			#접속포트
screen_size = 0.7			#스크린샷 크기
screen_width = 1920			#스크린샷 가로길이
screen_height = 1080			#스크린샷 세로길이
send_queue = Queue()
img_data = Image.open('화면대기.png')

def key_name(key):			#qt 키 이벤트 이름 반환함수
	if(key == QtCore.Qt.Key_A): return("a")
	elif(key == QtCore.Qt.Key_B): return("b")
	elif(key == QtCore.Qt.Key_C): return("c")
	elif(key == QtCore.Qt.Key_D): return("d")
	elif(key == QtCore.Qt.Key_E): return("e")
	elif(key == QtCore.Qt.Key_F): return("f")
	elif(key == QtCore.Qt.Key_G): return("g")
	elif(key == QtCore.Qt.Key_H): return("h")
	elif(key == QtCore.Qt.Key_I): return("i")
	elif(key == QtCore.Qt.Key_J): return("j")
	elif(key == QtCore.Qt.Key_K): return("k")
	elif(key == QtCore.Qt.Key_L): return("l")
	elif(key == QtCore.Qt.Key_M): return("m")
	elif(key == QtCore.Qt.Key_N): return("n")
	elif(key == QtCore.Qt.Key_O): return("o")
	elif(key == QtCore.Qt.Key_P): return("p")
	elif(key == QtCore.Qt.Key_Q): return("q")
	elif(key == QtCore.Qt.Key_R): return("r")
	elif(key == QtCore.Qt.Key_S): return("s")
	elif(key == QtCore.Qt.Key_T): return("t")
	elif(key == QtCore.Qt.Key_U): return("u")
	elif(key == QtCore.Qt.Key_V): return("v")
	elif(key == QtCore.Qt.Key_W): return("w")
	elif(key == QtCore.Qt.Key_X): return("x")
	elif(key == QtCore.Qt.Key_Y): return("y")
	elif(key == QtCore.Qt.Key_Z): return("z")
	elif(key == QtCore.Qt.Key_0): return("0")
	elif(key == QtCore.Qt.Key_1): return("1")
	elif(key == QtCore.Qt.Key_2): return("2")
	elif(key == QtCore.Qt.Key_3): return("3")
	elif(key == QtCore.Qt.Key_4): return("4")
	elif(key == QtCore.Qt.Key_5): return("5")
	elif(key == QtCore.Qt.Key_6): return("6")
	elif(key == QtCore.Qt.Key_7): return("7")
	elif(key == QtCore.Qt.Key_8): return("8")
	elif(key == QtCore.Qt.Key_9): return("9")
	elif(key == QtCore.Qt.Key_Space): return("space")
	elif(key == QtCore.Qt.Key_Backspace): return("backspace")
	elif(key == QtCore.Qt.Key_Delete): return("delete")
	elif(key == QtCore.Qt.Key_Return): return("enter")
	elif(key == QtCore.Qt.Key_Shift): return("shift")
	elif(key == QtCore.Qt.Key_Control): return("ctrl")
	elif(key == QtCore.Qt.Key_Alt): return("alt")
	elif(key == QtCore.Qt.Key_Up): return("up")
	elif(key == QtCore.Qt.Key_Down): return("down")
	elif(key == QtCore.Qt.Key_Left): return("left")
	elif(key == QtCore.Qt.Key_Right): return("right")
	elif(key == QtCore.Qt.Key_Semicolon): return(";")
	elif(key == QtCore.Qt.Key_Greater): return(">")
	elif(key == QtCore.Qt.Key_Equal): return("=")
	elif(key == QtCore.Qt.Key_Plus): return("+")
	elif(key == QtCore.Qt.Key_Minus): return("-")
	elif(key == QtCore.Qt.Key_Question): return("?")
	elif(key == QtCore.Qt.Key_Tab): return("tab")



class send_type():
	def __init__(self, type, a, b, time):
		super().__init__()
		self.type = type
		self.first_data = a
		self.sec_data = b
		self.time = time

class win(QtWidgets.QMainWindow):
	def __init__(self, size=1.0, on_top=False):
		super().__init__()
		self.opacity=1								#투명도
		self.name = "원격제어"						#창 이름
		self.size = size							#크기
		self.on_top = on_top						#항상 위에 있을지
		self.run_watch = 0							#실행타이머
		self.rel_cursur_xy = [0,0]			#커서 좌표
		self.rel_cusur_ratio = [0,0]		#커서 상대위치%
		self.is_left_clicked = False
		self.mouse_movetime = 0
		self.setupUi()

	def imagemanager_pil(self):			#이미지 변경
		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(self.rel_cursur_xy[0] - self.rel_cusur_ratio[0]*self.label.width(), self.rel_cursur_xy[1] - self.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.setWindowTitle(self.name)						#윈도우 제목 지정
		self.label = QtWidgets.QLabel(self.centralWidget)

		self.setMouseTracking(True)
		self.centralWidget.setMouseTracking(True)
		self.label.setMouseTracking(True)

		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()
		self.show()

	def resizeEvent(self, event):
		self.width = self.centralWidget.frameGeometry().width()
		self.height = self.centralWidget.frameGeometry().height()

	def wheelEvent(self, event):			#마우스휠 확대
		if (keyboard.is_pressed('ctrl')):			#컨트롤을 누를경우 크기조정
			self.rel_cursur_xy = [event.x(), event.y()]		#커서 위치 저장
			self.rel_cusur_ratio = [(event.x()-self.label.x())/self.label.width(), (event.y()-self.label.y())/self.label.height()]	#커서 상대위치% 저장
			print (self.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
		else:
			send_queue.put(send_type("mouse_wheel", event.angleDelta().y(), 0, self.run_watch))	#큐 추가


	def mousePressEvent(self, event):
		global send_queue, screen_width, screen_height
		self.rel_cursur_xy = [event.x(), event.y()]		#커서 위치 저장
		self.rel_cusur_ratio = [(event.x()-self.label.x())/self.label.width(), (event.y()-self.label.y())/self.label.height()]	#커서 상대위치% 저장
		if (keyboard.is_pressed('ctrl')):
			if (event.button() == QtCore.Qt.LeftButton):
				self.is_left_clicked = True
		else:
			mouse_x = int((event.x()-self.label.x())/self.label.width()*screen_width)	#마우스 좌표 계산
			mouse_y = int((event.y()-self.label.y())/self.label.height()*screen_height)
			if (event.button() == QtCore.Qt.LeftButton):
				send_queue.put(send_type("mouse_left_down", mouse_x, mouse_y, self.run_watch))	#큐 추가
			elif (event.button() == QtCore.Qt.RightButton):
				send_queue.put(send_type("mouse_right_down", mouse_x, mouse_y, self.run_watch))	#큐 추가


	def mouseMoveEvent(self, event):
		global send_queue, screen_width, screen_height
		if (keyboard.is_pressed('ctrl') and self.is_left_clicked):
			self.label.move(self.label.x() + (event.x() - self.rel_cursur_xy[0]), self.label.y() + (event.y() - self.rel_cursur_xy[1]))
			self.rel_cursur_xy = [event.x(), event.y()]		#커서 위치 저장
			self.rel_cusur_ratio = [(event.x()-self.label.x())/self.label.width(), (event.y()-self.label.y())/self.label.height()]	#커서 상대위치% 저장
		elif (not keyboard.is_pressed('ctrl')):
			if (self.label.x() < event.x() < self.label.x()+self.label.width() and self.label.y() < event.y() < self.label.y()+self.label.height()):
				mouse_x = int((event.x()-self.label.x())/self.label.width()*screen_width)	#마우스 좌표 계산
				mouse_y = int((event.y()-self.label.y())/self.label.height()*screen_height)
				send_queue.put(send_type("mouse_move", mouse_x, mouse_y, self.run_watch))	#큐 추가

	def mouseReleaseEvent(self, event):
		global send_queue, screen_width, screen_height
		if (keyboard.is_pressed('ctrl')):
			if (event.button() == QtCore.Qt.LeftButton):
				self.is_left_clicked = False
		else:
			mouse_x = int((event.x()-self.label.x())/self.label.width()*screen_width)	#마우스 좌표 계산
			mouse_y = int((event.y()-self.label.y())/self.label.height()*screen_height)
			if (event.button() == QtCore.Qt.LeftButton):
				send_queue.put(send_type("mouse_left_up", mouse_x, mouse_y, self.run_watch))	#큐 추가
			elif (event.button() == QtCore.Qt.RightButton):
				send_queue.put(send_type("mouse_right_up", mouse_x, mouse_y, self.run_watch))	#큐 추가

	def keyPressEvent(self, event):
		send_queue.put(send_type("key_press", key_name(event.key()), 0, self.run_watch))	#큐 추가
	def keyReleaseEvent(self, event):
		send_queue.put(send_type("key_release", key_name(event.key()), 0, self.run_watch))	#큐 추가

	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_screen(client_socket, addr):		#데이터 받기 함수
	global screen_size, screen_width, screen_height, img_data
	length = 0
	a = 0
	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)	#이미지 저장
		except Exception as ex:
			print(ex)
		finally:
			end = time.process_time()		#끝 시간 기록
			print("receive -> " + str(length) + " : " + str(a) + " : " + str(end - start))		#소요시간 출력




def send(client_socket, addr):
	global send_queue
	length = 0
	last_time = 0
	while (True):
		start = time.process_time()		#시작시간 기록`
		try:
			if (send_queue.qsize() != 0):
				send_data = send_queue.get()		#큐에서 꺼내옴
				if (send_data.type == "mouse_move"):	#드래그라면 0.3초마다 전송
					if (last_time + 0.3 > send_data.time or send_queue.qsize() == 0): continue
					else: last_time = send_data.time
				data = str(send_data.type) + ":" + str(send_data.first_data) + ":" + str(send_data.sec_data)
				data = data.encode()
				length = len(data)
				client_socket.sendall(length.to_bytes(4, byteorder="little"))		#데이터 크기 전송
				client_socket.sendall(data)				#데이터 전송
		except Exception as ex:
			print(ex)
		finally:
			end = time.process_time()		#끝 시간 기록
			#print("send -> " + str(length) + " : " + str(end - start))		#소요시간 출력
		time.sleep(0.01)


if __name__ == '__main__':
	client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	client_socket.connect((HOST, PORT))			#접속

	th_send = threading.Thread(target=send, args = (client_socket, HOST))		#전송함수 쓰레드
	th_send.start()
	th_receive_screen = threading.Thread(target=receive_screen, args = (client_socket, HOST))		#받기함수 쓰레드
	th_receive_screen.start()

	app = QtWidgets.QApplication(sys.argv)
	window = win(size=1, on_top=False)
	window.run()
	sys.exit(app.exec_())

 

실행 영상이다.

 

Comments