diff --git a/profile_examples/Portal 2 coop.sccprofile b/profile_examples/Portal 2 coop.sccprofile index 7af88bf5c..14867ff51 100644 --- a/profile_examples/Portal 2 coop.sccprofile +++ b/profile_examples/Portal 2 coop.sccprofile @@ -36,11 +36,6 @@ "doubleclick": {}, "hold": { "action": "menu('Default.menu')" - }, - "modes": { - "RT": { - "action": "shell('sc-controller')" - } } }, "LB": { diff --git a/profile_examples/README.md b/profile_examples/README.md index 30dce2b54..375000dc8 100644 --- a/profile_examples/README.md +++ b/profile_examples/README.md @@ -1 +1 @@ -Copy to ~/.config/scc/profiles/ +Drag and drop link on main window to import profile. \ No newline at end of file diff --git a/profile_examples/Retro City Rampage - in vehicle.sccprofile b/profile_examples/Retro City Rampage - in vehicle.sccprofile deleted file mode 100644 index b74d52419..000000000 --- a/profile_examples/Retro City Rampage - in vehicle.sccprofile +++ /dev/null @@ -1,64 +0,0 @@ -{ - "buttons": { - "A": { - "action": "button(Keys.KEY_ENTER)", - "name": "Exit Vehicle" - }, - "B": { - "action": "button(Keys.KEY_LEFTSHIFT)", - "name": "Shoot / Horn" - }, - "C": { - "action": "menu('Default.menu')" - }, - "LGRIP": { - "action": "profile('Retro City Rampage - on foot') and osd('On Foot')", - "name": "Switch to on foot controlls" - }, - "LPAD": { - "action": "button(Keys.BTN_THUMBL)" - }, - "RB": { - "action": "button(Keys.KEY_PAGEDOWN)", - "name": "Next Weapon" - }, - "RPAD": { - "action": "button(Keys.BTN_THUMBR)" - }, - "START": { - "action": "button(Keys.KEY_ESC)", - "name": "Pause/Menu" - }, - "X": { - "action": "button(Keys.KEY_LEFTCTRL)", - "name": "Pickup/Throw" - }, - "Y": { - "action": "button(Keys.BTN_WEST)" - } - }, - "gyro": {}, - "menus": {}, - "pad_left": {}, - "pad_right": { - "X": { - "action": "axis(Axes.ABS_X)" - }, - "Y": { - "action": "raxis(Axes.ABS_Y)" - }, - "sensitivity": [3.8, 1.0, 1.0] - }, - "stick": { - "action": "dpad(None, None, button(Keys.KEY_A), button(Keys.KEY_D))", - "name": "Steer" - }, - "trigger_left": { - "action": "button(Keys.KEY_S)", - "name": "Brake" - }, - "trigger_right": { - "action": "button(Keys.KEY_W)", - "name": "Accelerate" - } -} \ No newline at end of file diff --git a/profile_examples/Retro City Rampage - on foot.sccprofile b/profile_examples/Retro City Rampage - on foot.sccprofile deleted file mode 100644 index a3e23d870..000000000 --- a/profile_examples/Retro City Rampage - on foot.sccprofile +++ /dev/null @@ -1,62 +0,0 @@ -{ - "buttons": { - "A": { - "action": "button(Keys.KEY_ENTER)", - "name": "Cover/Enter" - }, - "B": { - "action": "button(Keys.KEY_SPACE)", - "name": "Jump" - }, - "C": { - "action": "menu('Default.menu')" - }, - "LB": { - "action": "button(Keys.KEY_PAGEUP)", - "name": "Weapon Menu" - }, - "LGRIP": { - "action": "profile('Retro City Rampage - in vehicle') and osd('In Vehicle')" - }, - "LPAD": { - "action": "button(Keys.BTN_THUMBL)" - }, - "RB": { - "action": "button(Keys.KEY_PAGEDOWN)", - "name": "Next Weapon" - }, - "RPAD": { - "action": "button(Keys.BTN_THUMBR)" - }, - "START": { - "action": "button(Keys.KEY_ESC)", - "name": "Pause/Menu" - }, - "X": { - "action": "button(Keys.KEY_LEFTCTRL)", - "name": "Pickup/Throw" - }, - "Y": { - "action": "button(Keys.BTN_WEST)" - } - }, - "gyro": {}, - "menus": {}, - "pad_left": { - "action": "dpad(button(Keys.KEY_W), button(Keys.KEY_S), button(Keys.KEY_A), button(Keys.KEY_D))", - "name": "Movement" - }, - "pad_right": {}, - "stick": { - "action": "dpad(button(Keys.KEY_W), button(Keys.KEY_S), button(Keys.KEY_A), button(Keys.KEY_D))", - "name": "Movement" - }, - "trigger_left": { - "action": "button(Keys.KEY_LEFTSHIFT)", - "name": "Attack" - }, - "trigger_right": { - "action": "button(Keys.KEY_LEFTSHIFT)", - "name": "Attack" - } -} \ No newline at end of file diff --git a/profile_examples/Tomb Raider 2013.sccprofile b/profile_examples/Tomb Raider 2013.sccprofile index dd9795f70..912ac8947 100644 --- a/profile_examples/Tomb Raider 2013.sccprofile +++ b/profile_examples/Tomb Raider 2013.sccprofile @@ -70,6 +70,7 @@ "name": "Melee" } }, + "gyro": {}, "left_pad": { "action": "dpad(button(Keys.KEY_1), button(Keys.KEY_2), button(Keys.KEY_3), button(Keys.KEY_4))", "name": "Weapon Switch" diff --git a/profile_examples/firefox.sccprofile b/profile_examples/firefox.sccprofile index c920b6b98..20335b96d 100644 --- a/profile_examples/firefox.sccprofile +++ b/profile_examples/firefox.sccprofile @@ -31,11 +31,6 @@ "doubleclick": {}, "hold": { "action": "menu('Default.menu')" - }, - "modes": { - "RT": { - "action": "shell('sc-controller')" - } } }, "LB": { diff --git a/scc/gui/app.py b/scc/gui/app.py index 514036912..a985e0da1 100644 --- a/scc/gui/app.py +++ b/scc/gui/app.py @@ -29,7 +29,7 @@ from scc.config import Config import scc.osd.menu_generators -import os, sys, platform, json, logging +import os, sys, platform, json, urllib, logging log = logging.getLogger("App") class App(Gtk.Application, UserDataManager, BindingEditor): @@ -102,7 +102,8 @@ def setup_widgets(self): # Drag&drop target self.builder.get_object("content").drag_dest_set(Gtk.DestDefaults.ALL, [ - Gtk.TargetEntry.new("text/uri-list", Gtk.TargetFlags.OTHER_APP, 0) + Gtk.TargetEntry.new("text/uri-list", Gtk.TargetFlags.OTHER_APP, 0), + Gtk.TargetEntry.new("text/plain", Gtk.TargetFlags.OTHER_APP, 0) ], Gdk.DragAction.COPY ) @@ -1084,19 +1085,56 @@ def load_profile_selection(self): def on_drag_data_received(self, widget, context, x, y, data, info, time): """ Drag-n-drop handler """ + uri = None if str(data.get_data_type()) == "text/uri-list": # Only file can be dropped here if len(data.get_uris()): uri = data.get_uris()[0] + elif str(data.get_data_type()) == "text/plain": + # This can be anything, so try to extract uri from it + lines = str(data.get_data()).split("\n") + if len(lines) > 0: + first = lines[0] + if first.startswith("http://") or first.startswith("https://") or first.startswith("ftp://"): + # I don't like other protocols + uri = first + if uri: + from scc.gui.importexport.dialog import Dialog + giofile = None + if uri.startswith("file://"): giofile = Gio.File.new_for_uri(uri) - if giofile.get_path(): - path = giofile.get_path() - from scc.gui.importexport.dialog import Dialog - if Dialog.is_supported(path): - ied = Dialog(self) - ied.show(self.window) - # Skip first screen and try to import this file - ied.import_file(giofile.get_path()) + else: + # Local file can be used directly, remote has to + # be downloaded first + if uri.startswith("https://github.com/"): + # Convert link to repository display to link to raw file + uri = (uri + .replace("https://github.com/", "https://raw.githubusercontent.com/") + .replace("/blob/", "/") + ) + name = urllib.unquote(".".join(uri.split("/")[-1].split(".")[0:-1])) + remote = Gio.File.new_for_uri(uri) + tmp, stream = Gio.File.new_tmp("%s.XXXXXX" % (name,)) + stream.close() + if remote.copy(tmp, Gio.FileCopyFlags.OVERWRITE, None, None): + # Sucessfully downloaded + log.info("Downloaded '%s'" % (uri,)) + giofile = tmp + else: + # Failed. Just do nothing + return + if giofile.get_path(): + path = giofile.get_path() + filetype = Dialog.determine_type(path) + if filetype: + log.info("Importing '%s'..." % (filetype)) + log.debug("(type %s)" % (filetype,)) + ied = Dialog(self) + ied.show(self.window) + # Skip first screen and try to import this file + ied.import_file(path, filetype = filetype) + else: + log.error("Unknown file type: '%s'..." % (path,)) class UndoRedo(object): diff --git a/scc/gui/importexport/dialog.py b/scc/gui/importexport/dialog.py index fef16207c..b92d626b3 100644 --- a/scc/gui/importexport/dialog.py +++ b/scc/gui/importexport/dialog.py @@ -11,7 +11,7 @@ from import_vdf import ImportVdf from import_sccprofile import ImportSccprofile -import sys, os, logging +import sys, os, tarfile, logging, json log = logging.getLogger("IE.Dialog") class Dialog(Editor, ComboSetter, Export, ImportVdf, ImportSccprofile): @@ -29,16 +29,48 @@ def __init__(self, app): @staticmethod - def is_supported(filename): + def determine_type(filename): """ - Returns True if passed file can be imported. + Detects and returns type of passed file, if it can be imported. + Returns one of 'sccprofile', 'sccprofile.tar.gz', 'vdf', 'vdffz' + or None if type is not supported. """ - # Currently decided base on extension - return (filename.endswith(".sccprofile") - or filename.endswith(".sccprofile.tar.gz") - or filename.endswith(".vdf") - or filename.endswith(".vdffz") - ) + f = file(filename, 'rb').read(1024) + try: + if f.decode("utf-8").strip(" \t\r\n").startswith("{"): + # Looks like json + data = json.loads(open(filename, "r").read()) + if "buttons" in data and "gyro" in data: + return 'sccprofile' + if "GameName" in data and "FileName" in data: + return 'vdffz' + except: + # Definitelly not json + pass + + if f[0:2] == b"\x1f\x8b": + # gzip, hopefully tar.gz + try: + tar = tarfile.open(filename, "r:gz") + names = [ x.name for x in tar ] + any_profile = any([ x.endswith(".sccprofile") for x in names ]) + if any_profile and "profile-name" in names: + return "sccprofile.tar.gz" + except: + # Not a tarball + pass + + # Rest is decided by extension + if filename.endswith(".sccprofile.tar.gz"): + return "sccprofile.tar.gz" + if filename.endswith(".vdf"): + return "vdf" + # Fallbacks if above fails + if filename.endswith(".sccprofile"): + return "sccprofile" + if filename.endswith(".vdffz"): + return "vdffz" + return None @staticmethod @@ -54,18 +86,19 @@ def check_name(name): return True - def import_file(self, filename): + def import_file(self, filename, filetype = None): """ Attempts to import passed file. Switches to apropriate page automatically, or, if file cannot be imported, does nothing. """ - if filename.endswith(".sccprofile"): + filetype = filetype or Dialog.determine_type(filename) + if filetype == "sccprofile": self.import_scc(filename=filename) - elif filename.endswith(".sccprofile.tar.gz"): + elif filetype == "sccprofile.tar.gz": self.import_scc_tar(filename=filename) - elif filename.endswith(".vdf") or filename.endswith(".vdffz"): + elif filetype in ("vdf", "vdffz"): self.import_vdf(filename=filename)