Initial commit

This commit is contained in:
Lukas 2021-01-16 14:20:03 +01:00
parent 96d841d4f3
commit 52ae16df3b
5 changed files with 564 additions and 0 deletions

37
fillscreen.py Normal file
View File

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'C:\Users\lmahler\Desktop\Dump\02_Python\Grail\fillscreen.ui'
#
# Created by: PyQt4 UI code generator 4.11.4
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_View(object):
def setupUi(self, View):
View.setObjectName(_fromUtf8("View"))
View.resize(294, 166)
View.setWindowOpacity(0.3)
View.setStyleSheet(_fromUtf8(""))
self.retranslateUi(View)
QtCore.QMetaObject.connectSlotsByName(View)
def retranslateUi(self, View):
View.setWindowTitle(_translate("View", "Grail.exe", None))

25
fillscreen.ui Normal file
View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>View</class>
<widget class="QWidget" name="View">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>294</width>
<height>166</height>
</rect>
</property>
<property name="windowTitle">
<string>Grail.exe</string>
</property>
<property name="windowOpacity">
<double>0.300000000000000</double>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
</widget>
<resources/>
<connections/>
</ui>

386
grail.py Normal file
View File

@ -0,0 +1,386 @@
"""
#*********************************#
#* Grail *#
#*********************************#
#* Author: Lukas Mahler *#
#* Copyright: (C)2018-2020 *#
#* Version: 0.5.0 *#
#* Date: 25.12.2020 *#
#*********************************#
# Instant Upload Screenshot-Tool #
# based on the idea of Gyazoo. #
# -> MS Windows only! #
#*********************************#
"""
__author__ = "Lukas Mahler"
__copyright__ = "Copyright 2018-2021"
__version__ = "0.5.1"
__date__ = "16.01.2021"
__email__ = "lm@ankerlab.de"
__status__ = "Development"
try:
# Defaults
import os
import sys
import webbrowser
from io import BytesIO
from shutil import rmtree
from getpass import getpass
from tempfile import gettempdir
from socket import create_connection
from configparser import ConfigParser
from time import gmtime, strftime, sleep
# GUI
from PyQt4 import QtGui, QtCore, Qt
from PyQt4.QtCore import QProcess, QPoint
from fillscreen import Ui_View
# Customs
import win32gui
import win32clipboard
import paramiko
from desktopmagic.screengrab_win32 import getRectAsImage
except ImportError as e:
print("ERROR: Missing Module |||", e)
sleep(10)
os.system("PAUSE>NUL")
sys.exit()
###################################################################################################
###################################################################################################
class Fillscreen(QtGui.QWidget, Ui_View):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
###############################################################################################
###############################################################################################
# Bastelstüble wegen Transparentem Window welches aber den Input auf unterliegendes blockieren soll
# self.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
# self.setBackgroundRole(QtGui.QPalette.Base)
# self.setAttribute(Qt.Qt.WA_NoSystemBackground, True)
self.setWindowFlags(
QtCore.Qt.WindowStaysOnTopHint |
QtCore.Qt.FramelessWindowHint |
QtCore.Qt.X11BypassWindowManagerHint
)
# self.istransparent = True
self.setupUi(self)
# self.setWindowOpacity(0.3)
# Qcolor = "rgba(255, 255, 255, 0)"
# self.setStyleSheet("background-color:{0}".format(Qcolor))
###############################################################################################
###############################################################################################
# Change Cursor to select tool on Startup
self.change_cursor()
self.begin = QtCore.QPoint()
self.end = QtCore.QPoint()
self.startingpoint = QtCore.QPoint()
self.endpoint = QtCore.QPoint()
def showFullscreen(self):
self.showMaximized()
# https://stackoverflow.com/a/44468898/5593051
def paintEvent(self, event):
qp = QtGui.QPainter(self)
# br = QtGui.QBrush(QtGui.QColor(100, 10, 10, 40))
qp.setPen(QtGui.QPen(QtCore.Qt.red, 3))
qp.drawRect(QtCore.QRect(self.begin, self.end))
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.RightButton:
print("DEBUG: Exiting on Right Click Request")
app.quit()
else:
self.begin = event.pos()
self.end = event.pos()
# self.startingpoint = event.pos()
self.startingpoint = get_real_pos()
# print("Startpoint:",self.startingpoint)
self.update()
def mouseMoveEvent(self, event):
self.end = event.pos()
self.update()
def mouseReleaseEvent(self, event):
self.begin = event.pos()
self.end = event.pos()
self.endpoint = event.pos()
self.endpoint = get_real_pos()
# print("Endpoint:",self.endpoint)
self.update()
do_screen(self.startingpoint, self.endpoint)
app.quit()
@staticmethod
def change_cursor():
# http://doc.qt.io/archives/qt-4.8/qcursor.html
QtGui.QApplication.setOverrideCursor(QtCore.Qt.CrossCursor)
# QtGui.QApplication.restoreOverrideCursor()
###################################################################################################
###################################################################################################
def read_ini(rootdir):
myini = {}
config = ConfigParser()
try:
config.read_file(open(rootdir+'settings.ini'))
except Exception as e:
print("ERROR: Couldn't locate a 'settings.ini' |||", e)
# Creating new blank ini
with open(rootdir+'settings.ini', 'w') as f:
txt = "[MAIN]\n" \
"# MODE EITHER ONLINE OR OFFLINE\nMODE = OFFLINE\n\n\n" \
"# IF MODE IS ONLINE PROVIDE THE SFTP INFO BELOW\n" \
"[ONLINE]\n\n" \
"# URL EITHER IS AN IP OR A FQDN\nURL = \n\n" \
"# PORT IS THE SSH PORT\nPORT = 22\n\n" \
"# THE TIME IN SECONDS TO TRY TO CONNECT\nTIMEOUT = 5\n\n" \
"# YOUR SSH USER, THIS CAN BE BLANK TO GET ASKED INTERACTIVELY\nUSR = \nPASS = \n\n" \
"# THE LOCAL SERVER PATH WHERE TO PUT THE SCREENSHOT\nPTH = ./Screens/\n\n" \
"# THIS SHOULD BE THE FULL ONLINE URL\nWHERE = \n\n\n" \
"# IF MODE IS OFFLINE YOU MAY CHANGE THE SETTINGS BELOW\n" \
"[OFFLINE]\n\n" \
"WHERE = "
f.write(txt)
mode = config.get('MAIN', 'MODE')
if mode == "ONLINE":
url = config.get('ONLINE', 'URL')
port = int(config.get('ONLINE', 'PORT'))
timeout = int(config.get('ONLINE', 'TIMEOUT'))
usr = config.get('ONLINE', 'USR')
passw = config.get('ONLINE', 'PASS')
pth = config.get('ONLINE', 'PTH')
where = config.get('ONLINE', 'WHERE')
myini.update(
mode = mode,
URL = url,
PORT = port,
TIMEOUT = timeout,
USR = usr,
PASS = passw,
PTH = pth,
WHERE = where
)
elif mode == "OFFLINE":
where = config.get('OFFLINE', 'WHERE')
myini.update(
mode = mode,
WHERE = where
)
else:
print("ERROR: you seem to be having a corrupt or broken 'settings.ini' please recheck!")
return myini
def get_real_pos():
flags, hcursor, (x, y) = win32gui.GetCursorInfo()
return {"x": x, "y": y}
def calc_frame(startinspot, endspot):
# If dragging began from top
if startinspot['x'] < endspot['x']:
left = startinspot['x']
right = endspot['x']
top = startinspot['y']
bottom = endspot['y']
# If dragging began from bottom
else:
left = endspot['x']
right = startinspot['x']
top = endspot['y']
bottom = startinspot['y']
# print("DEBUG: Start:",left,"x",top)
# print("DEBUG: End :",right,"x",bottom)
if abs(left-right) <= 10 and abs(top-bottom) <= 10:
coordinates = None
else:
coordinates = (left, top, right+1, bottom+1)
return coordinates
def do_screen(start, end):
""" This Function somehow is the Brain now """
# QtGui.QPixmap.grabWindow(QtGui.QApplication.desktop().winId()).save('screenshot.jpg', 'jpg')
# Hide all windows to take a Screenshot of the underlying interface
for window in windows:
window.hide()
frame = calc_frame(start, end)
print("Took Snapshot on Coordinates:", frame)
# https://github.com/python-pillow/Pillow/issues/1547
# https://github.com/ludios/Desktopmagic/blob/master/desktopmagic/screengrab_win32.py
# im = ImageGrab.grab(bbox=frame)
if not frame:
print("ERROR: Screenshots below 5x5 px not allowed, exiting...")
sleep(5)
return
im = getRectAsImage(frame)
send_to_clipboard(im)
timestamp = strftime("%Y-%m-%d_%H.%M.%S")
tempdir = gettempdir()+"\\Screens"
# print("DEBUG:",tempdir)
if not os.path.exists(tempdir):
os.makedirs(tempdir)
tempsave = '{0}\\{1}.png'.format(tempdir, 'Screen_'+timestamp)
im.save(tempsave)
if settings['MODE'] == "ONLINE":
upload_to_url(tempsave)
else:
save_to_local(tempsave)
clean_temp(tempdir)
# https://stackoverflow.com/questions/34322132/copy-image-to-clipboard-in-python3
def send_to_clipboard(im=None):
if im:
output = BytesIO()
im.convert("RGB").save(output, "BMP")
data = output.getvalue()[14:]
output.close()
win32clipboard.OpenClipboard()
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data)
win32clipboard.CloseClipboard()
print("Copied to Clipboard")
else:
win32clipboard.EmptyClipboard()
print("Cleared Clipboard")
# https://stackoverflow.com/questions/432385/sftp-in-python-platform-independent
def upload_to_url(tempsave):
# SFTP INFO
url = settings['URL']
port = settings['PORT']
timeout = settings['TIMEOUT']
usr = settings['USR']
passw = settings['PASS']
pth = settings['PTH'] + os.path.basename(tempsave)
where = settings['WHERE'] + os.path.basename(tempsave)
# Test Connection first or go into fallback
try:
s = create_connection((url, port), timeout)
s.close()
except Exception as e:
print("ERROR: Connection failed. |||", e)
print("\nFalling back to local save...")
save_to_local(tempsave, fallback=1)
return
# If not provided ask for Username & Password
if not usr:
usr = input("Please provide your Username: ")
if not passw:
passw = getpass("Please provide the password for {0}: ".format(usr))
try:
transport = paramiko.Transport((url, port))
transport.connect(username=usr, password=passw)
except Exception as e:
print("ERROR: SSH2 negotiation or authentication failed. |||", e)
print("\nFalling back to local save...")
save_to_local(tempsave, fallback=1)
return
print("Starting to Upload...")
sftp = paramiko.SFTPClient.from_transport(transport)
sftp.put(tempsave, pth)
sftp.close()
transport.close()
print("Screen was Uploaded: URL: " + where)
open_url(where)
def save_to_local(tempsave, fallback=None):
if not fallback:
where = settings['WHERE']
else:
print(" -> Did fallback!")
config = ConfigParser()
config.read_file(open(root+'_settings.ini'))
where = config.get('OFFLINE', 'WHERE')
if not where:
print("No local Screens folder provided, creating one...")
where = os.path.dirname(os.path.abspath(__file__))+"\\Screens"
print("OFFLINE MODE: Screenshot Folder:", where)
if not os.path.exists(where):
os.makedirs(where)
src = tempsave
dst = where + "\\" + os.path.basename(tempsave)
# print("Source:",src,"\nDestination:",dst)
os.rename(src, dst)
def open_url(where):
webbrowser.open(where, new=0, autoraise=True)
def clean_temp(tempdir):
if os.path.exists(tempdir):
print("Cleaning up the tempdir [", tempdir, "]...")
rmtree(tempdir, ignore_errors=True)
###################################################################################################
###################################################################################################
if __name__ == '__main__':
root = os.path.dirname(os.path.abspath(__file__))+"\\"
settings = read_ini(root)
if os.path.exists(root+"qt.conf"):
try:
os.remove(root+"qt.conf")
except Exception as e:
print("DEBUG:", e)
app = QtGui.QApplication(sys.argv)
windows = []
# https://stackoverflow.com/questions/51058236/create-qt-windows-based-on-number-of-list-items
for i in range(QtGui.QApplication.desktop().screenCount()):
topLeft = QtGui.QApplication.desktop().screenGeometry(i).topLeft()
window = Fillscreen()
window.move(topLeft)
window.showFullscreen()
windows.append(window)
app.exec_()

11
grail_build.cfg Normal file
View File

@ -0,0 +1,11 @@
[ship]
param = --onefile --windowed
icon = C:\Users\lukas\Desktop\Dump\08_Resources\camera.ico
[debug]
param = --onefile
icon = C:\Users\lukas\Desktop\Dump\08_Resources\camera.ico
[test]
param = --onedir --noupx --add-binary msvcp140.dll;.
icon = C:\Users\lukas\Desktop\Dump\08_Resources\camera.ico

105
selector.py Normal file
View File

@ -0,0 +1,105 @@
"""
This subfile to Grail handles window settings and calculations.
-> Only works on MS Windows!
"""
__author__ = "Lukas Mahler"
__copyright__ = "Copyright 2018-2020"
__version__ = "1.0.0"
__date__ = "25.11.2020"
__email__ = "lm@ankerlab.de"
__status__ = "Development"
import win32api
import win32gui
def getCursorPos():
flags, hcursor, (x, y) = win32gui.GetCursorInfo()
return {"x": x, "y": y}
def getMonitors():
monitors = []
for hMonitor, hdcMonitor, pyRect in win32api.EnumDisplayMonitors():
monitors.append(pyRect)
#print(monitors)
return monitors
def getResolution(monitors):
resolutions = {}
index = 0
for monitor in monitors:
x_from = monitor[0]
x_to = monitor[2]
y_from = monitor[1]
y_to = monitor[3]
res_x = x_to - x_from
res_y = y_to - y_from
res = (res_x, res_y)
resolutions['Monitor'+str(index)] = res
index += 1
return resolutions
def getScreenInfo():
return {"x": win32api.GetSystemMetrics(0), "y": win32api.GetSystemMetrics(1)}
def getStartingpoint():
return getCursorPos()
def getEndpoint():
return getCursorPos()
def getRectangle():
# Source: https://stackoverflow.com/a/41930485/5593051
state_left = win32api.GetKeyState(0x01) # Left button down = 0 or 1. Button up = -127 or -128
state_right = win32api.GetKeyState(0x02) # Right button down = 0 or 1. Button up = -127 or -128
while True:
a = win32api.GetKeyState(0x01)
b = win32api.GetKeyState(0x02)
if a != state_left: # Button state changed
state_left = a
if a < 0:
print('Left Button Pressed')
pos_start = getStartingpoint()
else:
print('Left Button Released')
pos_end = getEndpoint()
break
print(pos_start)
print(pos_end)
calcScreenshotFrame(pos_start, pos_end)
def calcScreenshotFrame(pos_start, pos_end):
# If dragging began from top
if pos_start['x'] < pos_end['x']:
left = pos_start['x']
right = pos_end['x']
top = pos_start['y']
bottom = pos_end['y']
# If dragging began from bottom
else:
left = pos_end['x']
right = pos_start['x']
top = pos_end['y']
bottom = pos_start['y']
print("Screenshot needs to be taken from: x:", left, "->", right, "and y:", top, "->", bottom)
if __name__ == '__main__':
print("This is a Subfile from grail, it can't be executed on its own.")
exit()