#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Created on Mon Nov 18 12:04:53 2024 @author: angoosh references: https://pygobject.gnome.org/tutorials/gtk4/introduction.html https://docs.gtk.org/gtk3/index.html#classes """ import sys import os import gi import subprocess import json from cryptography.fernet import Fernet gi.require_version('Gtk', '4.0') gi.require_version('Adw', '1') from gi.repository import Gtk, Adw, Gdk, Gio import keyring APPID = "com.angoosh.RDPConnect" HOMEDIR = os.path.expanduser('~') VERSION = "1.3.0" conn_info = {} hist_info = {} settings = {} settings["extra_params"] = {} fernet = "" def load_keys(): global fernet cryptoKey = "" try: with open(HOMEDIR+"/.config/rdpconnect/.key", "r") as keyfile: cryptoKey = str.encode(keyfile.readline()) print("Encription key loaded") except: cryptoKey = Fernet.generate_key() with open(HOMEDIR+"/.config/rdpconnect/.key", "w") as keyfile: keyfile.write(cryptoKey.decode("utf-8")) print("Encription key generated") fernet = Fernet(cryptoKey) def load_config(): global conn_info, settings, hist_info loaded_json = "" try: with open(HOMEDIR+"/.config/rdpconnect/history.json", "r") as history_file: for line in history_file: loaded_json += line hist_info = json.loads(loaded_json) last_conn = hist_info[max(hist_info)] conn_info["user"] = last_conn.split('@')[0] conn_info["ip"] = last_conn.split('@')[1] conn_info["passwd"] = keyring.get_password("com.angoosh.RDPConnect", last_conn) except: try: with open(HOMEDIR+"/.config/rdpconnect/connection.json", "r") as connection_file: for line in connection_file: loaded_json += line conn_info = json.loads(loaded_json) conn_info["passwd"] = fernet.decrypt(str.encode(conn_info["passwd"])).decode() except: print("[WARN] FILE: "+HOMEDIR+"/.config/rdpconnect/connection.json doesn't exist") loaded_json = "" try: with open(HOMEDIR+"/.config/rdpconnect/settings.json", "r") as settings_file: for line in settings_file: loaded_json += line settings = json.loads(loaded_json) except: print("[WARN] FILE: "+HOMEDIR+"/.config/rdpconnect/settings.json doesn't exist") class HistoryWindow(Gtk.Window): global hist_info def __init__(self, **kargs): super().__init__(**kargs, title='History') self.set_default_size(100, 300) self.scroll = Gtk.ScrolledWindow() self.scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) self.grid = Gtk.Grid() self.scroll.set_child(self.grid) index = 0 for i in hist_info: self.addRow(hist_info[i], index) index += 1 self.set_child(self.scroll) self.show() def addRow(self, entry, index): runb = Gtk.Button(label=entry) runb.connect("clicked", self.apply_contents) self.grid.attach(runb, 0, index, 1, 1) def apply_contents(self, button): conn_info["user"] = button.get_label().split('@')[0] conn_info["ip"] = button.get_label().split('@')[1] try: conn_info["passwd"] = keyring.get_password("com.angoosh.RDPConnect", button.get_label()) except: pass class PreferencesWindow(Gtk.Window): global settings def __init__(self, **kwargs): super().__init__(**kwargs) self.builder = Gtk.Builder(self) self.builder.add_from_file("/app/preferences.ui") self.resx = self.builder.get_object("resx") self.resy = self.builder.get_object("resy") self.monitors = self.builder.get_object("monitors") self.fullscreen = self.builder.get_object("fullscreen") self.xfree = self.builder.get_object("xfree") self.sound = self.builder.get_object("sound") self.mic = self.builder.get_object("mic") self.dynres = self.builder.get_object("dynres") self.clipboard = self.builder.get_object("clipboard") try: self.clipboard.set_state(settings["extra_params"]["clipboard"]) self.clipboard.set_active(settings["extra_params"]["clipboard"]) except: pass try: self.dynres.set_state(settings["extra_params"]["dynamic-resolution"]) self.dynres.set_active(settings["extra_params"]["dynamic-resolution"]) except: pass try: self.mic.set_state(settings["extra_params"]["microphone"]) self.mic.set_active(settings["extra_params"]["microphone"]) except: pass try: self.sound.set_state(settings["extra_params"]["sound"]) self.sound.set_active(settings["extra_params"]["sound"]) except: pass try: if settings["rdp_bin"] == "xfreerdp": self.xfree.set_state(True) self.xfree.set_active(True) except: pass try: self.fullscreen.set_state(settings["extra_params"]["f"]) self.fullscreen.set_active(settings["extra_params"]["f"]) except: pass try: self.monitors.get_buffer().set_text(str(settings["extra_params"]["monitors"]), len(settings["extra_params"]["monitors"])) except: pass try: self.resx.get_buffer().set_text(str(settings["extra_params"]["w"]), len(settings["extra_params"]["w"])) except: pass try: self.resy.get_buffer().set_text(str(settings["extra_params"]["h"]), len(settings["extra_params"]["h"])) except: pass pref_window = self.builder.get_object("pref_window") pref_window.show() def f_resx(self, app): settings["extra_params"]["w"] = self.resx.get_buffer().get_text() def f_resy(self, app): settings["extra_params"]["h"] = self.resy.get_buffer().get_text() def monconf(self, app): settings["extra_params"]["monitors"] = self.monitors.get_buffer().get_text() def clipboard(self, app, state): if state: settings["extra_params"]["clipboard"] = True else: settings["extra_params"]["clipboard"] = False def dynres(self, app, state): if state: settings["extra_params"]["dynamic-resolution"] = True else: settings["extra_params"]["dynamic-resolution"] = False def microphone(self, app, state): if state: settings["extra_params"]["microphone"] = True else: settings["extra_params"]["microphone"] = False def sound(self, app, state): if state: settings["extra_params"]["sound"] = True else: settings["extra_params"]["sound"] = False def xfree(self, app, state): if state: settings["rdp_bin"] = "xfreerdp" else: settings["rdp_bin"] = "sdl-freerdp" def fscr(self, app, state): if state: settings["extra_params"]["f"] = True else: settings["extra_params"]["f"] = False def on_destroy(self, app): print("Saving settings config to "+HOMEDIR+"/.config/rdpconnect/settings.json") with open(HOMEDIR+"/.config/rdpconnect/settings.json", "w") as settings_file: js = json.dumps(settings, sort_keys=True, indent=4, separators=(',', ': ')) settings_file.write(js) class MyApp(Adw.Application): global hist_info def __init__(self, **kwargs): super().__init__(**kwargs) self.connect('activate', self.on_activate) def on_activate(self, app): builder = Gtk.Builder() builder.add_from_file("/app/"+APPID+".ui") buildermenu = Gtk.Builder() buildermenu.add_from_file("/app/menu.ui") menu_model = buildermenu.get_object('app-menu') css_provider = Gtk.CssProvider() if Adw.StyleManager().get_default().get_dark(): css_provider.load_from_file(Gio.File.new_for_path("/app/style-dark.css")) else: css_provider.load_from_file(Gio.File.new_for_path("/app/style-light.css")) Gtk.StyleContext.add_provider_for_display(Gdk.Display.get_default(), css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) connect = builder.get_object("connect") connect.connect("clicked", self.connectRDP) self.ip = builder.get_object("ip") self.user = builder.get_object("user") self.passwd = builder.get_object("pass") self.save_conf = builder.get_object("save_conf") try: if settings["save_conn"]: self.ip.get_buffer().set_text(str(conn_info["ip"]), len(conn_info["ip"])) self.user.get_buffer().set_text(str(conn_info["user"]), len(conn_info["user"])) self.passwd.get_buffer().set_text(str(conn_info["passwd"]), len(conn_info["passwd"])) self.save_conf.set_active(True) except: pass #menu config self.win = builder.get_object("main_window") header_bar = Gtk.HeaderBar() self.win.set_titlebar(header_bar) menubutton = Gtk.MenuButton(menu_model=menu_model, icon_name='open-menu-symbolic') header_bar.pack_end(menubutton) self.win.set_application(self) self.win.present() def do_startup(self): Gtk.Application.do_startup(self) action = Gio.SimpleAction(name='preferences') action.connect('activate', self.on_preferences) self.add_action(action) action = Gio.SimpleAction(name='about') action.connect('activate', self.on_about) self.add_action(action) #action = Gio.SimpleAction(name='history') #action.connect('activate', self.on_history) #self.add_action(action) def on_about(self, action, param): about_dialog = Gtk.AboutDialog(transient_for=self.win, modal=True) about_dialog.set_copyright("Antonin Kaplan") about_dialog.set_program_name("RDP Connect") about_dialog.set_license_type(Gtk.License(16)) about_dialog.set_logo_icon_name("com.angoosh.RDPConnect") about_dialog.set_version(VERSION) about_dialog.set_website("https://gitea.farmdash.org/angoosh/Flatpaks") about_dialog.present() def on_preferences(self, action, param): PreferencesWindow() def on_history(self, action, param): HistoryWindow() def saveConnConf(self): if settings["save_conn"]: password = conn_info["passwd"] useratip = conn_info["user"] + "@" + conn_info["ip"] with open(HOMEDIR+"/.config/rdpconnect/history.json", "w") as hist_file: hist_id = str(int(max(hist_info, default=0)) + 1) hist_info[hist_id] = useratip js = json.dumps(hist_info, sort_keys=True, indent=4, separators=(',', ': ')) hist_file.write(js) try: keyring.set_password("com.angoosh.RDPConnect", useratip, password) except: conn_info["passwd"] = fernet.encrypt(password.encode()).decode("utf-8") print("Saving connection config to "+HOMEDIR+"/.config/rdpconnect/connection.json") with open(HOMEDIR+"/.config/rdpconnect/connection.json", "w") as connection_file: js = json.dumps(conn_info, sort_keys=True, indent=4, separators=(',', ': ')) connection_file.write(js) conn_info["passwd"] = password else: with open(HOMEDIR+"/.config/rdpconnect/connection.json", "w") as connection_file: connection_file.write("") print("Saving settings config to "+HOMEDIR+"/.config/rdpconnect/settings.json") with open(HOMEDIR+"/.config/rdpconnect/settings.json", "w") as settings_file: js = json.dumps(settings, sort_keys=True, indent=4, separators=(',', ': ')) settings_file.write(js) def connectRDP(self, button): conn_info["ip"] = self.ip.get_buffer().get_text() conn_info["user"] = self.user.get_buffer().get_text() conn_info["passwd"] = self.passwd.get_buffer().get_text() if self.save_conf.get_active(): settings["save_conn"] = True else: settings["save_conn"] = False if not "rdp_bin" in settings: settings["rdp_bin"] = "sdl-freerdp" self.saveConnConf() extra_params = [] for item in settings["extra_params"]: #check if is boolean if type(settings["extra_params"][item]) == type(True): if settings["extra_params"][item]: extra_params.append("/"+str(item)) else: if str(settings["extra_params"][item]) != "": print(str(settings["extra_params"][item])) extra_params.append("/"+str(item)+":"+str(settings["extra_params"][item])) print(extra_params) try: print("Running", settings["rdp_bin"]) subprocess.run([settings["rdp_bin"], "/cert:ignore", "/v:"+str(conn_info["ip"]), "/u:"+str(conn_info["user"]), "/p:"+str(conn_info["passwd"])]+extra_params) except: print("Running sdl-freerdp") subprocess.run(["sdl-freerdp", "/cert:ignore", "/v:"+str(conn_info["ip"]), "/u:"+str(conn_info["user"]), "/p:"+str(conn_info["passwd"])]+extra_params) if not os.path.isdir(HOMEDIR+"/.config/rdpconnect"): os.makedirs(HOMEDIR+"/.config/rdpconnect") load_keys() load_config() app = MyApp(application_id=APPID) app.run(sys.argv)