Grail/grail.py

461 lines
15 KiB
Python

__author__ = "Lukas Mahler"
__copyright__ = "Copyright 2018-2021"
__version__ = "0.6.1"
__date__ = "23.05.2021"
__email__ = "lm@ankerlab.de"
__status__ = "Development"
try:
# Defaults
import os
import sys
import webbrowser
from io import BytesIO
from requests import head
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 PyQt5 import QtGui, QtWidgets, QtCore, Qt
from PyQt5.QtCore import QProcess, QPoint
from fillscreen import Ui_View
# Customs
import owncloud
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(QtWidgets.QWidget, Ui_View):
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
###############################################################################################
###############################################################################################
# Monkeycode for trying to block input for content under the transparent window.
# 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
QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.CrossCursor)
# QtGui.QApplication.restoreOverrideCursor()
###################################################################################################
###################################################################################################
def sslurl(url, ssl):
if ssl:
url = "https://" + url
else:
url = "http://" + url
return url
def read_ini(rootdir):
myini = {}
config = ConfigParser(inline_comment_prefixes=";")
try:
config.read_file(open(rootdir + 'settings.ini'))
except FileNotFoundError:
print("ERROR: Couldn't locate a 'settings.ini'")
print("--> Trying to create new blank one.")
# Creating new blank ini
with open(rootdir + 'settings.ini', 'w') as f:
txt = (
"# MAIN SETTINGS\n"
"[MAIN]\n"
"MODE = LOCAL ; MODE EITHER SFTP, NEXTCLOUD OR LOCAL\n"
"TIMEOUT = 5 ; TIMEOUT WHEN TESTING SFTP CONNECTION\n"
"SSL = TRUE ; USING HTTP (FALSE) OR HTTPS (TRUE) URLS\n"
"\n"
"# PROVIDE SFTP INFO BELOW\n"
"[SFTP]\n"
"URL = ; URL EITHER IS AN IP OR A FQDN\n"
"PORT = ; PORT IS THE SSH PORT\n"
"USR = ; YOUR SSH USER, THIS CAN BE BLANK TO GET ASKED INTERACTIVELY\n"
"PASS = ; YOUR SSH USERS PW, THIS CAN BE BLANK TO GET ASKED INTERACTIVELY\n"
"PTH = ; THE LOCAL SERVER PATH WHERE TO PUT THE SCREENSHOT\n"
"WHERE = ; THIS SHOULD BE THE FULL ONLINE URL\n"
"\n"
"# PROVIDE NEXTCLOUD INFO BELOW\n"
"[NEXTCLOUD]\n"
"URL = ; URL EITHER IS AN IP OR A FQDN OF A NEXTCLOUD SERVER\n"
"USR = ; YOUR NEXTCLOUD USER, THIS CAN BE BLANK TO GET ASKED INTERACTIVELY\n"
"PASS = ; YOUR NEXTCLOUD USERS PW, THIS CAN BE BLANK TO GET ASKED INTERACTIVELY\n"
"PTH = ; THE NEXTCLOUD FOLDER PATH WHERE TO PUT THE SCREENSHOT\n"
"\n"
"# IF MODE IS LOCAL YOU MAY CHANGE THE SETTINGS BELOW\n"
"[LOCAL]\n"
"WHERE = ; THIS SHOULD BE A FULL LOCAL PATH\n"
)
f.write(txt)
config.read_file(open(rootdir + 'settings.ini'))
# General Settings
timeout = int(config.get('MAIN', 'TIMEOUT'))
ssl = config.get('MAIN', 'SSL')
mode = config.get('MAIN', 'MODE')
if mode == "SFTP":
url = config.get(mode, 'URL')
port = int(config.get(mode, 'PORT'))
usr = config.get(mode, 'USR')
passw = config.get(mode, 'PASS')
pth = config.get(mode, 'PTH')
where = config.get(mode, 'WHERE')
myini.update(
MODE = mode,
URL = url,
PORT = port,
TIMEOUT = timeout,
USR = usr,
PASS = passw,
PTH = pth,
WHERE = sslurl(where, ssl)
)
elif mode == "NEXTCLOUD":
url = sslurl(config.get(mode, 'URL'), ssl)
usr = config.get(mode, 'USR')
passw = config.get(mode, 'PASS')
pth = config.get(mode, 'PTH')
myini.update(
MODE = mode,
URL = url,
USR = usr,
PASS = passw,
PTH = pth,
)
elif mode == "LOCAL":
where = config.get('LOCAL', '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, timestamp)
im.save(tempsave)
if settings['MODE'] == "SFTP":
upload_to_url_sftp(tempsave)
elif settings['MODE'] == "NEXTCLOUD":
upload_to_url_nextcloud(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_sftp(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("\n --> Falling back to local mode")
save_to_local(tempsave, fallback=1)
return
# If not provided ask for Username & Password
if not usr:
usr = input("Please provide a 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("\n--> Falling back to local mode")
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 upload_to_url_nextcloud(tempsave):
# NEXTCLOUD INFO
url = settings['URL']
usr = settings['USR']
passw = settings['PASS']
pth = settings['PTH'] + os.path.basename(tempsave)
# Test Connection first or go into fallback
r = head(url)
httpc = str(r.status_code)[0]
if httpc == "4" or httpc == "5":
print("ERROR: HTTP Statuscode for URL [{0}] is [{1}]".format(url, r.status_code))
print("\n--> Falling back to local mode")
save_to_local(tempsave, fallback=1)
return
# If not provided ask for Username & Password
if not usr:
usr = input("Please provide a username: ")
if not passw:
passw = getpass("Please provide the password for {0}: ".format(usr))
try:
nxt = owncloud.Client(url)
nxt.login(usr, passw)
except Exception as e:
print("ERROR: ", e)
print("\n--> Falling back to local mode")
save_to_local(tempsave, fallback=1)
return
# Had to monkeypatch the pyocclient libary here to stop AttributeErrors on 'share_file_with_link' calls
# (https://github.com/owncloud/pyocclient/issues/263)
print("Starting to Upload...")
nxt.put_file(pth, tempsave)
link_info = nxt.share_file_with_link(pth)
where = link_info.get_link()
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('LOCAL', 'WHERE')
if not where:
print("No local Screens folder provided, creating one...")
where = os.path.dirname(os.path.abspath(__file__))+"\\Screens"
print("LOCAL 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)
os.startfile(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 = QtWidgets.QApplication(sys.argv)
windows = []
# https://stackoverflow.com/questions/51058236/create-qt-windows-based-on-number-of-list-items
for i in range(QtWidgets.QApplication.desktop().screenCount()):
topLeft = QtWidgets.QApplication.desktop().screenGeometry(i).topLeft()
window = Fillscreen()
window.move(topLeft)
window.showFullscreen()
windows.append(window)
app.exec_()