diff --git a/README.md b/README.md index 104cea7..22627ad 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ # EzyCA +Easily create Certificate Authorities and sign a server PEM and key for use. -Easily create Certificate Authorities and sign a server PEM and key for use. \ No newline at end of file +# Requirements +* Need Python 3+ +* Need OpenSSL +* Need PyGObject +# Images +![1 Interface CA.... ](images/pic1.png) +![2 Interface Server Cert.... ](images/pic2.png) diff --git a/images/pic1.png b/images/pic1.png new file mode 100644 index 0000000..7543f72 Binary files /dev/null and b/images/pic1.png differ diff --git a/images/pic2.png b/images/pic2.png new file mode 100644 index 0000000..d8de3f2 Binary files /dev/null and b/images/pic2.png differ diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..df89b7b --- /dev/null +++ b/src/__init__.py @@ -0,0 +1,33 @@ +# Python imports +import inspect + + +# Gtk imports + + +# Application imports +from utils import Settings +from signal_classes import Signals + + +class Main: + def __init__(self, args): + settings = Settings() + builder = settings.returnBuilder() + + # Gets the methods from the classes and sets to handler. + # Then, builder connects to any signals it needs. + classes = [Signals(settings)] + + handlers = {} + for c in classes: + methods = None + try: + methods = inspect.getmembers(c, predicate=inspect.ismethod) + handlers.update(methods) + except Exception as e: + pass + + builder.connect_signals(handlers) + window = settings.createWindow() + window.show() diff --git a/src/__main__.py b/src/__main__.py new file mode 100644 index 0000000..d13dbaf --- /dev/null +++ b/src/__main__.py @@ -0,0 +1,32 @@ +#!/usr/bin/python3 + + +# Python imports +import argparse +from setproctitle import setproctitle + +# Gtk imports +import gi, faulthandler, signal +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk as gtk +from gi.repository import GLib + +# Application imports +from __init__ import Main + + +if __name__ == "__main__": + try: + setproctitle('EzyCA') + GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, gtk.main_quit) + faulthandler.enable() # For better debug info + parser = argparse.ArgumentParser() + # Add long and short arguments + parser.add_argument("--file", "-f", default="default", help="JUST SOME FILE ARG.") + + # Read arguments (If any...) + args = parser.parse_args() + main = Main(args) + gtk.main() + except Exception as e: + print( repr(e) ) diff --git a/src/resources/Main_Window.glade b/src/resources/Main_Window.glade new file mode 100644 index 0000000..44775bf --- /dev/null +++ b/src/resources/Main_Window.glade @@ -0,0 +1,399 @@ + + + + + + True + False + gtk-file + 3 + + + + 800 + 600 + False + EzyCA + x509_cert_64x64.png + + + True + False + vertical + + + True + False + + + True + False + 10 + Target Directory: + + + False + True + 0 + + + + + True + False + 10 + 10 + 15 + 15 + select-folder + True + False + False + Target Directory... + + + + True + True + 1 + + + + + False + True + 0 + + + + + True + False + + + True + False + False + 25 + 10 + x509_cert.png + + + False + False + 0 + + + + + True + True + + + True + False + vertical + 15 + + + True + True + 2 + Country Name (2 letter code eg, US) + alpha + + + + False + True + 0 + + + + + True + True + 32 + State or Province Name (full name eg, 'North Carolina') + alpha + + + + False + True + 1 + + + + + True + True + Locality/City Name (full name eg, 'Durham') + alpha + + + + False + True + 2 + + + + + True + True + Organization Name (eg, company 'Microsoft') + alpha + + + + False + True + 3 + + + + + True + True + Organizational Unit (eg, division 'Microsoft Developent') + alpha + + + False + True + 4 + + + + + True + True + Common Name (e.g. server FQDN or YOUR name 'Microsoft CA') + alpha + + + False + True + 5 + + + + + True + True + Email Address (eg, 'no-reply@microsoft.com') + email + + + + False + True + 6 + + + + + True + + + + + True + False + CA Conf + + + False + + + + + True + False + vertical + 15 + + + True + True + 2 + Country Name (2 letter code eg, US) + alpha + + + False + True + 0 + + + + + True + True + 32 + State or Province Name (full name eg, 'North Carolina') + alpha + + + False + True + 1 + + + + + True + True + Locality/City Name (full name eg, 'Durham') + alpha + + + False + True + 2 + + + + + True + True + Organization Name (eg, company 'Microsoft') + alpha + + + False + True + 3 + + + + + True + True + Common Name (e.g. server FQDN or YOUR name 'Microsoft Test Server') + alpha + + + False + True + 4 + + + + + True + True + Email Address (eg, 'no-reply@microsoft.com') + email + + + False + True + 5 + + + + + True + False + 10 + Below, add domain names each on a new line... + True + False + + + + + + + False + True + 6 + + + + + True + True + in + False + + + True + True + textview_buffer + False + alpha + True + + + + + True + True + 7 + + + + + 1 + True + + + + + True + False + Server Conf + + + 1 + False + + + + + + + + + + + True + True + 1 + + + + + True + True + 1 + + + + + Generate + True + True + True + 10 + 10 + 15 + 15 + generate_img + True + True + + + + False + True + 2 + + + + + + diff --git a/src/resources/stylesheet.css b/src/resources/stylesheet.css new file mode 100644 index 0000000..f3c855c --- /dev/null +++ b/src/resources/stylesheet.css @@ -0,0 +1,19 @@ +/* * { + background: rgba(0, 0, 0, 0.14); + background: rgba(0, 0, 0, 1); + color: rgba(255, 255, 255, 1); +} + +notebook > header { + border-color: rgba(0, 232, 255, 0.64); +} + +* selection { + background-color: rgba(75, 0, 116, 0.65); + color: rgba(255, 255, 255, 0.5); +} + +textarea.view { + border-style: solid; + border-color: rgba(255, 255, 255, 1); +} */ diff --git a/src/resources/x509_cert.png b/src/resources/x509_cert.png new file mode 100644 index 0000000..b10ed45 Binary files /dev/null and b/src/resources/x509_cert.png differ diff --git a/src/resources/x509_cert_64x64.png b/src/resources/x509_cert_64x64.png new file mode 100644 index 0000000..464b293 Binary files /dev/null and b/src/resources/x509_cert_64x64.png differ diff --git a/src/signal_classes/Signals.py b/src/signal_classes/Signals.py new file mode 100644 index 0000000..a3fff2e --- /dev/null +++ b/src/signal_classes/Signals.py @@ -0,0 +1,130 @@ +# Python imports +import threading, subprocess, os + +# Gtk imports + +# Application imports +from .mixins import * + + +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs).start() + + return wrapper + + +class Signals(Utils, CAGenerator, ServerCertGenerator): + def __init__(self, settings): + self.settings = settings + self.builder = self.settings.returnBuilder() + + self.ca_entry_vbox = self.builder.get_object("ca_entry_vbox") + self.server_entry_vbox = self.builder.get_object("server_entry_vbox") + self.textview_buffer = self.builder.get_object("textview_buffer") + + self.target_path = None + + + def set_work_directory(self, widget, data=None): + self.target_path = widget.get_filename() + + + def generate_ca_and_server_certs(self, widget): + print("Generating CA and signed Server Certs...") + ca_data = [] + server_cert_data = [] + ca_entries = self.ca_entry_vbox.get_children() + server_entries = self.server_entry_vbox.get_children() + + self.fill_data_block(ca_data, ca_entries) + self.fill_data_block(server_cert_data, server_entries) + + buffer = self.textview_buffer + domains = buffer.get_text( + buffer.get_start_iter(), + buffer.get_end_iter(), + True + ).strip().split("\n") + + assert(self.target_path != None and self.target_path != ''), "A target work directory must be set!" + assert(len(ca_data) == len(ca_entries)), "All CA entries must be filled!" + assert(len(server_cert_data) == (len(server_entries) - 2) ), "All Server Cert entries must be filled!" + + + self.step_1_create_ca_settings(self.target_path, ca_data) + self.step_2_create_signing_ca_settings(self.target_path, ca_data) + self.step_3_create_openssl_server_cert_settings(self.target_path, server_cert_data, domains) + self.step_4_generate_all(self.target_path) + + + def step_4_generate_all(self, target_path): + print("Step 4: Running generation process...") + opensslcacnf = target_path + "/openssl-ca-settings.cnf" + opensslsigningcasettingscnf = target_path + "/openssl-signing-ca-settings.cnf" + opensslservercnf = target_path + "/openssl-server-cert.cnf" + + signedcertsdir = target_path + "/signed_certs" + cacertpem = target_path + "/cacert.pem" + servercertpem = target_path + "/servercert.pem" + servercertcsr = target_path + "/servercert.csr" + indexfile = target_path + "/index.txt" + serialfile = target_path + "/serial.txt" + + + os.chdir(target_path) + os.mkdir(signedcertsdir) + with open(indexfile, 'a') as f: + f.close() + with open(serialfile, 'w') as f: + f.write("01") + + print("\t\tCreating CA cert key...") + # openssl req -batch -x509 -days 10000 -config ./openssl-ca.cnf -newkey + # rsa:4096 -sha256 -nodes -out cacert.pem -outform PEM + command = ["openssl", "req", "-batch", "-x509", "-days", "10000", "-config", opensslcacnf, "-newkey", "rsa:4096", + "-sha256", "-nodes", "-out", cacertpem, "-outform", "PEM"] + proc = subprocess.Popen(command, stdout=subprocess.PIPE) + proc.wait() + # You can dump the certificate with the following. + # openssl x509 -in cacert.pem -text -noout + + + print("\t\tCreating Server cert key pare...") + # openssl req -batch -config ./openssl-server.cnf -newkey rsa:2048 + # -sha256 -nodes -out servercert.csr -outform PEM + command = ["openssl", "req", "-batch", "-config", opensslservercnf, "-newkey", "rsa:4096", "-sha256", "-nodes", + "-out", servercertcsr, "-outform", "PEM"] + proc = subprocess.Popen(command, stdout=subprocess.PIPE) + proc.wait() + # You can inspect. + # openssl req -text -noout -verify -in servercert.csr + + + print("\t\tCertifying Server cert with our CA...") + # openssl ca -batch -config ./openssl-ca.cnf -policy signing_policy + # -extensions signing_req -out servercert.pem -infiles servercert.csr + command = ["openssl", "ca", "-batch", "-config", opensslsigningcasettingscnf, + "-policy", "signing_policy", "-extensions", "signing_req", "-out", + servercertpem, "-infiles", "servercert.csr"] + proc = subprocess.Popen(command, stdout=subprocess.PIPE) + proc.wait() + # Finally, you can inspect your freshly minted certificate with the following: + # openssl x509 -in servercert.pem -text -noout + + + + def set_server_country(self, widget, data=None): + self.builder.get_object("svrCountry").set_text(widget.get_text().strip()) + + def set_server_state(self, widget, data=None): + self.builder.get_object("svrState").set_text(widget.get_text().strip()) + + def set_server_city(self, widget, data=None): + self.builder.get_object("svrCity").set_text(widget.get_text().strip()) + + def set_server_org(self, widget, data=None): + self.builder.get_object("svrOrg").set_text(widget.get_text().strip()) + + def set_server_email(self, widget, data=None): + self.builder.get_object("svrEmail").set_text(widget.get_text().strip()) diff --git a/src/signal_classes/__init__.py b/src/signal_classes/__init__.py new file mode 100644 index 0000000..159e1e6 --- /dev/null +++ b/src/signal_classes/__init__.py @@ -0,0 +1,5 @@ +""" + Gtk Bound Signal Module +""" +from .mixins import * +from .Signals import Signals diff --git a/src/signal_classes/mixins/CAGenerator.py b/src/signal_classes/mixins/CAGenerator.py new file mode 100644 index 0000000..c23282c --- /dev/null +++ b/src/signal_classes/mixins/CAGenerator.py @@ -0,0 +1,210 @@ +# Python imports +import threading, subprocess, os + +# Gtk imports + +# Application imports + + +class CAGenerator: + """Empty docstring for CAGenerator""" + ca_settings = """# ----------------------------- NOTE +# Remember to disable very bottom when generating first ca key and pem. +# Check notes at [ CA_default ] section as well! +# ----------------------------- NOTE + +HOME = {} +RANDFILE = $ENV::HOME/.rnd + +#################################################################### +[ ca ] +default_ca = CA_default # The default ca section + +[ CA_default ] +default_days = 3000 # How long to certify for +default_crl_days = 3000 # How long before next CRL +default_md = sha256 # Use public key default MD +preserve = no # Keep passed DN ordering +x509_extensions = ca_extensions # The extensions to add to the cert +email_in_dn = no # Don't concat the email in the DN +copy_extensions = copy # Required to copy SANs from CSR to cert + +# ca_default_additional_block_marker + +#################################################################### +[ req ] +default_bits = 4096 +default_keyfile = cakey.pem +distinguished_name = ca_distinguished_name +x509_extensions = ca_extensions +string_mask = utf8only + + +#################################################################### +[ ca_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = {} +stateOrProvinceName = State or Province Name (full name eg, 'North Carolina') +stateOrProvinceName_default = {} +localityName = Locality/City Name (full name eg, 'Durham') +localityName_default = {} +organizationName = Organization Name (eg, company 'Microsoft') +organizationName_default = {} +organizationalUnitName = Organizational Unit (eg, division) +organizationalUnitName_default = {} +commonName = Common Name (e.g. server FQDN or YOUR name) +commonName_default = {} +emailAddress = Email Address (eg, 'no-reply@microsoft.com') +emailAddress_default = {} + + +#################################################################### +[ ca_extensions ] +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always, issuer +basicConstraints = critical, CA:true +keyUsage = keyCertSign, cRLSign + +# Extensions for a typical CA +# PKIX recommendation. +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer +basicConstraints = critical,CA:true + +# Key usage: This is typical for a CA certificate. However since it will prevent +# it being used as an test self-signed certificate it is best left out by default. +# keyUsage = cRLSign, keyCertSign + +# Some might want this also... +# nsCertType = sslCA, emailCA + +# subjectAltName=email:copy # Include email address in subject alt name: another PKIX recommendation +# issuerAltName=issuer:copy # Copy issuer details + +# DER hex encoding of an extension: Beware experts only! +# obj=DER:02:03 # Where 'obj' is a standard or added object +# basicConstraints= critical, DER:30:03:01:01:FF # You can even override a supported extension. + + +#################################################################### +[ crl_ext ] +# CRL extensions. Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. +# issuerAltName = issuer:copy +authorityKeyIdentifier = keyid:always + + +#################################################################### +[ proxy_cert_ext ] +# These extensions should be added when creating a proxy certificate +# This goes against PKIX guidelines but some CAs do it and some software +# requires this to avoid interpreting an end user certificate as a CA. +basicConstraints=CA:FALSE + +# Here are some examples of the usage of nsCertType. If it is omitted +# the certificate can be used for anything *except* object signing. +# nsCertType = server # This is OK for an SSL server. +# nsCertType = objsign # For an object signing certificate this would be used. +# nsCertType = client, email # For normal client using this is typical. +# nsCertType = client, email, objsign # For everything including object signing. + +# This is typical in keyUsage for a client certificate. +# keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +# This will be displayed in Netscape's comment listbox. +nsComment = 'OpenSSL Generated Certificate' + +# PKIX recommendations harmless if included in all certificates. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer + +# This stuff is for subjectAltName and issuerAltname. +# subjectAltName = email:copy # Import the email address. +# subjectAltName = email:move # An alternative to produce certificates that aren't deprecated according to PKIX. +# issuerAltName = issuer:copy # Copy subject details + +#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem +#nsBaseUrl +#nsRevocationUrl +#nsRenewalUrl +#nsCaPolicyUrl +#nsSslServerName + +# This really needs to be in place for it to be a proxy certificate. +proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo + + +#################################################################### +[ tsa ] +default_tsa = tsa_config1 # The default TSA section (Which is set below...) + +[ tsa_config1 ] +# These are used by the TSA reply generation only. +dir = /etc/ssl # TSA root directory +serial = $dir/tsaserial # The current serial number +crypto_device = builtin # OpenSSL engine to use for signing +signer_cert = $dir/tsacert.pem # The TSA signing certificate (optional) +certs = $dir/cacert.pem # Certificate chain to include in reply (optional) +signer_key = $dir/private/tsakey.pem # The TSA private key (optional) +signer_digest = sha256 # Signing digest to use. (optional) +default_policy = tsa_policy1 # Policy if request did not specify it (optional) +other_policies = tsa_policy2, tsa_policy3 # Acceptable policies (optional) +digests = sha1, sha256, sha384, sha512 # Acceptable message digests +accuracy = secs:1, millisecs:500, microsecs:100 # (optional) +clock_precision_digits = 0 # Number of digits after dot. (optional) +ordering = yes # Is ordering defined for timestamps? (optional, default: no) +tsa_name = yes # Must the TSA name be included in the reply? (optional, default: no) +ess_cert_id_chain = no # Must the ESS cert id chain be included? (optional, default: no) +ess_cert_id_alg = sha1 # Algorithm to compute certificate identifier (optional, default: sha1) + +# ca_signing_block_marker +""" + + ca_default_additional_block = """base_dir = {} +certificate = $base_dir/cacert.pem # The CA certifcate +private_key = $base_dir/cakey.pem # The CA private key +new_certs_dir = $base_dir/signed_certs/ # Location for new certs after signing +database = $base_dir/index.txt # Database index file +serial = $base_dir/serial.txt # The current serial number +unique_subject = no # Set to 'no' to allow creation of several certificates with same subject. +""" + + ca_signing_block = """#################################################################### +[ signing_policy ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ signing_req ] +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer +basicConstraints = CA:FALSE +keyUsage = digitalSignature, keyEncipherment +""" + + + def step_1_create_ca_settings(self, path, data): + print("Step 1: Creating CA Settings...") + all_data = [path] + data + fpath = path + "/openssl-ca-settings.cnf" + output_str = self.ca_settings.format(*all_data) + + with open(fpath, "w") as f: + f.write(output_str) + + + def step_2_create_signing_ca_settings(self, path, data): + print("Step 2: Creating Signing CA Settings...") + all_data = [path] + data + fpath = path + "/openssl-signing-ca-settings.cnf" + output_str = self.ca_settings.format(*all_data) + ca_block = self.ca_default_additional_block.format(path) + + output_str = output_str.replace("# ca_default_additional_block_marker", ca_block) + output_str = output_str.replace("# ca_signing_block_marker", self.ca_signing_block) + + with open(fpath, "w") as f: + f.write(output_str) diff --git a/src/signal_classes/mixins/ServerCertGenerator.py b/src/signal_classes/mixins/ServerCertGenerator.py new file mode 100644 index 0000000..e989c9b --- /dev/null +++ b/src/signal_classes/mixins/ServerCertGenerator.py @@ -0,0 +1,83 @@ +# Python imports +import threading, subprocess, os + +# Gtk imports + +# Application imports + + +class ServerCertGenerator: + """Empty docstring for ServerCertGenerator""" + + server_cert_settings = """# ----------------------------- NOTE +HOME = {} +RANDFILE = $ENV::HOME/.rnd + + +#################################################################### +[ req ] +default_bits = 2048 +default_keyfile = privkey.pem +distinguished_name = server_distinguished_name +req_extensions = server_req_extensions +string_mask = utf8only + + +#################################################################### +[ server_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = {} +stateOrProvinceName = State or Province Name (full name eg, 'North Carolina') +stateOrProvinceName_default = {} +localityName = Locality/City Name (full name eg, 'Durham') +localityName_default = {} +organizationName = Organization Name (eg, company 'Microsoft') +organizationName_default = {} +commonName = Common Name (e.g. server FQDN or YOUR name) +commonName_default = {} +emailAddress = Email Address (eg, 'no-reply@microsoft.com') +emailAddress_default = {} + + +#################################################################### +[ server_req_extensions ] +subjectKeyIdentifier = hash +basicConstraints = CA:FALSE +keyUsage = digitalSignature, keyEncipherment +subjectAltName = @alternate_names +nsComment = "OpenSSL Generated Certificate" + +#################################################################### +[ alternate_names ] +# alternate_names_marker + + +# If you are developing and need to use your workstation as a server, then you may +# need to do the following for Chrome. Otherwise Chrome may complain a Common Name +# is invalid (ERR_CERT_COMMON_NAME_INVALID). I'm not sure what the relationship is +# between an IP address in the SAN and a CN in this instance. + +# IPv4 localhost +IP.1 = 0.0.0.0 +IP.2 = 127.0.0.1 + +# IPv6 localhost +IP.2 = ::1 +""" + + + def step_3_create_openssl_server_cert_settings(self, path, data, domains): + print("Step 3: Creating OpenSSL Server Cert Settings...") + all_data = [path] + data + fpath = path + "/openssl-server-cert.cnf" + output_str = self.server_cert_settings.format(*all_data) + domain_block = "" + i = 1 + + for domain in domains: + domain_block += "DNS.{} = {}".format(*(i, domain + "\n")) + i += 1 + + output_str = output_str.replace("# alternate_names_marker", domain_block) + with open(fpath, "w") as f: + f.write(output_str) diff --git a/src/signal_classes/mixins/Utils.py b/src/signal_classes/mixins/Utils.py new file mode 100644 index 0000000..741b27d --- /dev/null +++ b/src/signal_classes/mixins/Utils.py @@ -0,0 +1,35 @@ +# Python imports +import threading, subprocess, os + +# Gtk imports + +# Application imports + + +class Utils: + """Empty docstring for Utils""" + + def fill_data_block(self, target, data): + for entry in data: + text = '' + + if hasattr(entry, "get_children"): + entry = entry.get_children()[0] + + if hasattr(entry, "get_text"): + text = entry.get_text().strip() + + if text != '' and text != 'Below, add domain names each on a new line...': + target.append(text) + + def getClipboardData(self): + proc = subprocess.Popen(['xclip','-selection', 'clipboard', '-o'], stdout=subprocess.PIPE) + retcode = proc.wait() + data = proc.stdout.read() + return data.decode("utf-8").strip() + + def setClipboardData(self, data): + proc = subprocess.Popen(['xclip','-selection','clipboard'], stdin=subprocess.PIPE) + proc.stdin.write(data) + proc.stdin.close() + retcode = proc.wait() diff --git a/src/signal_classes/mixins/__init__.py b/src/signal_classes/mixins/__init__.py new file mode 100644 index 0000000..12f73cd --- /dev/null +++ b/src/signal_classes/mixins/__init__.py @@ -0,0 +1,3 @@ +from .Utils import Utils +from .CAGenerator import CAGenerator +from .ServerCertGenerator import ServerCertGenerator diff --git a/src/utils/Logger.py b/src/utils/Logger.py new file mode 100644 index 0000000..c7f294e --- /dev/null +++ b/src/utils/Logger.py @@ -0,0 +1,56 @@ +# Python imports +import os, logging + +# Application imports + + +class Logger: + def __init__(self): + pass + + def get_logger(self, loggerName = "NO_LOGGER_NAME_PASSED", createFile = True): + """ + Create a new logging object and return it. + :note: + NOSET # Don't know the actual log level of this... (defaulting or literally none?) + Log Levels (From least to most) + Type Value + CRITICAL 50 + ERROR 40 + WARNING 30 + INFO 20 + DEBUG 10 + :param loggerName: Sets the name of the logger object. (Used in log lines) + :param createFile: Whether we create a log file or just pump to terminal + + :return: the logging object we created + """ + + globalLogLvl = logging.DEBUG # Keep this at highest so that handlers can filter to their desired levels + chLogLevel = logging.CRITICAL # Prety musch the only one we change ever + fhLogLevel = logging.DEBUG + log = logging.getLogger(loggerName) + log.setLevel(globalLogLvl) + + # Set our log output styles + fFormatter = logging.Formatter('[%(asctime)s] %(pathname)s:%(lineno)d %(levelname)s - %(message)s', '%m-%d %H:%M:%S') + cFormatter = logging.Formatter('%(pathname)s:%(lineno)d] %(levelname)s - %(message)s') + + ch = logging.StreamHandler() + ch.setLevel(level=chLogLevel) + ch.setFormatter(cFormatter) + log.addHandler(ch) + + if createFile: + folder = "logs" + file = folder + "/application.log" + + if not os.path.exists(folder): + os.mkdir(folder) + + fh = logging.FileHandler(file) + fh.setLevel(level=fhLogLevel) + fh.setFormatter(fFormatter) + log.addHandler(fh) + + return log diff --git a/src/utils/Settings.py b/src/utils/Settings.py new file mode 100644 index 0000000..05b7b0f --- /dev/null +++ b/src/utils/Settings.py @@ -0,0 +1,84 @@ +# Python imports +import os + +# Gtk imports +import gi, cairo +gi.require_version('Gtk', '3.0') +gi.require_version('Gdk', '3.0') + +from gi.repository import Gtk as gtk +from gi.repository import Gdk as gdk + + +# Application imports + + +class Settings: + def __init__(self): + self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__)) + "/" + self.builder = gtk.Builder() + self.builder.add_from_file(self.SCRIPT_PTH + "../resources/Main_Window.glade") + + # 'Filters' + self.office = ('.doc', '.docx', '.xls', '.xlsx', '.xlt', '.xltx', '.xlm', + '.ppt', 'pptx', '.pps', '.ppsx', '.odt', '.rtf') + self.vids = ('.mkv', '.avi', '.flv', '.mov', '.m4v', '.mpg', '.wmv', + '.mpeg', '.mp4', '.webm') + self.txt = ('.txt', '.text', '.sh', '.cfg', '.conf') + self.music = ('.psf', '.mp3', '.ogg' , '.flac') + self.images = ('.png', '.jpg', '.jpeg', '.gif') + self.pdf = ('.pdf') + + + def createWindow(self): + # Get window and connect signals + window = self.builder.get_object("Main_Window") + window.connect("delete-event", gtk.main_quit) + self.setWindowData(window, False) + return window + + def setWindowData(self, window, paintable): + screen = window.get_screen() + visual = screen.get_rgba_visual() + + if visual != None and screen.is_composited(): + window.set_visual(visual) + + # bind css file + cssProvider = gtk.CssProvider() + cssProvider.load_from_path(self.SCRIPT_PTH + '../resources/stylesheet.css') + screen = gdk.Screen.get_default() + styleContext = gtk.StyleContext() + styleContext.add_provider_for_screen(screen, cssProvider, gtk.STYLE_PROVIDER_PRIORITY_USER) + + window.set_app_paintable(paintable) + if paintable: + window.connect("draw", self.area_draw) + + def getMonitorData(self): + screen = self.builder.get_object("Main_Window").get_screen() + monitors = [] + for m in range(screen.get_n_monitors()): + monitors.append(screen.get_monitor_geometry(m)) + + for monitor in monitors: + print("{}x{}+{}+{}".format(monitor.width, monitor.height, monitor.x, monitor.y)) + + return monitors + + def area_draw(self, widget, cr): + cr.set_source_rgba(0, 0, 0, 0.54) + cr.set_operator(cairo.OPERATOR_SOURCE) + cr.paint() + cr.set_operator(cairo.OPERATOR_OVER) + + + def returnBuilder(self): return self.builder + + # Filter returns + def returnOfficeFilter(self): return self.office + def returnVidsFilter(self): return self.vids + def returnTextFilter(self): return self.txt + def returnMusicFilter(self): return self.music + def returnImagesFilter(self): return self.images + def returnPdfFilter(self): return self.pdf diff --git a/src/utils/__init__.py b/src/utils/__init__.py new file mode 100644 index 0000000..415301e --- /dev/null +++ b/src/utils/__init__.py @@ -0,0 +1,6 @@ +""" + Utils module +""" + +from .Logger import Logger +from .Settings import Settings