diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..61846a6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+*tmp*
+*.gvd*
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3ea6bff
--- /dev/null
+++ b/README.md
@@ -0,0 +1,9 @@
+# ulauncher-remmina
+
+🖥 Ulauncher extension for quick access to [Remmina](https://remmina.org) profiles.
+
+You can enter several queries to match strings in profiles' descriptions (e.g. `r ssh stan`)
+
+Some icons from the [Numix project](https://github.com/numixproject) and [Remmina](https://github.com/FreeRDP/Remmina).
+
+
diff --git a/images/icon.svg b/images/icon.svg
new file mode 100644
index 0000000..7e15a98
--- /dev/null
+++ b/images/icon.svg
@@ -0,0 +1,148 @@
+
+
+
+
diff --git a/images/rdp.svg b/images/rdp.svg
new file mode 100644
index 0000000..6520ce4
--- /dev/null
+++ b/images/rdp.svg
@@ -0,0 +1,41 @@
+
diff --git a/images/ssh.svg b/images/ssh.svg
new file mode 100644
index 0000000..9e64490
--- /dev/null
+++ b/images/ssh.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..250491f
--- /dev/null
+++ b/main.py
@@ -0,0 +1,141 @@
+import os
+import logging
+import distutils.spawn
+from ulauncher.api.client.Extension import Extension
+from ulauncher.api.client.EventListener import EventListener
+from ulauncher.api.shared.event import KeywordQueryEvent
+from ulauncher.api.shared.item.ExtensionResultItem import ExtensionResultItem
+from ulauncher.api.shared.item.SmallResultItem import SmallResultItem
+from ulauncher.api.shared.action.RenderResultListAction import RenderResultListAction
+from ulauncher.api.shared.action.RunScriptAction import RunScriptAction
+
+logging.basicConfig()
+logger = logging.getLogger(__name__)
+
+# Initialize items cache and Remmina profiles path
+remmina_bin = ''
+# Locate Remmina profiles and binary
+remmina_profiles_path = "{}/.local/share/remmina".format(os.environ.get('HOME'))
+remmina_bin = distutils.spawn.find_executable('remmina')
+# This extension is useless without remmina
+if remmina_bin is None or remmina_bin == '':
+ logger.error('Remmina executable path could not be determined')
+ exit()
+# Check if Remmina profiles directory exists
+if not os.path.isdir(remmina_profiles_path):
+ logger.error("Remmina profiles directory doesn't exist ({})".format(remmina_profiles_path))
+ exit()
+
+
+class RemminaExtension(Extension):
+ def __init__(self):
+
+ super(RemminaExtension, self).__init__()
+ self.subscribe(KeywordQueryEvent, KeywordQueryEventListener())
+
+ def list_profiles(self, query):
+ profiles = []
+ items_cache = []
+ try:
+ # Get list of profile files from Remmina directory
+ for profile in os.listdir(remmina_profiles_path):
+ if profile.endswith(".remmina"):
+ profiles.append(os.path.join(remmina_profiles_path, profile))
+ # Get sorted list of profiles
+ temp = profiles
+ profiles = sorted(temp)
+ except Exception as e:
+ print('Failed getting profile files')
+
+ for p in profiles:
+ base = os.path.basename(p)
+ title = os.path.splitext(base)[0]
+ desc, proto = profile_details(p)
+ # Search for query inside filename and profile description
+ # Multiple strings can be used to search in description
+ # all() is used to achieve a AND search (include all keywords)
+ keywords = query.split(" ")
+ # if (query in base.lower()) or (query in desc.lower()):
+ if (query in base.lower()) or all(x in desc for x in keywords):
+ items_cache.append(create_item(title, proto, p, desc, p))
+
+ return items_cache
+
+
+class KeywordQueryEventListener(EventListener):
+ def on_event(self, event, extension):
+ # Get query
+ term = (event.get_argument() or '').lower()
+ # Display all items when query empty
+ profiles_list = extension.list_profiles(term)
+
+ return RenderResultListAction(profiles_list)
+
+
+def create_item(name, icon, keyword, description, on_enter):
+ return ExtensionResultItem(
+ name=name,
+ description=description,
+ icon='images/{}.svg'.format(icon),
+ on_enter=RunScriptAction('#!/usr/bin/env bash\n{} -c {}\n'.format(remmina_bin, on_enter), None)
+ )
+
+
+def profile_details(profile_path):
+ if os.path.isfile(profile_path):
+ with open(profile_path, "r") as f:
+ # Read profile file lines
+ lines = f.read().split("\n")
+ # Initialize strings
+ desc = name = username = group = proto = ""
+ # Parse lines for relevant details
+ for line in lines:
+ # Profile name
+ if line.startswith("name="):
+ elem = line.split("name=")
+ if len(elem[1]) > 0:
+ name = elem[1]
+ # Profile username (optional)
+ if "username=" in line:
+ elem = line.split("username=")
+ # if len(elem) > 1:
+ if len(elem[0]) == 0 and len(elem[1]) > 0:
+ username = elem[1]
+ elif len(elem[0]) > 0 and len(elem[1]) > 0:
+ username = elem[1]
+ # Profile server and port
+ if line.startswith("server="):
+ elem = line.split("server=")
+ if len(elem[1]) > 0:
+ server = elem[1]
+ # Profile group name
+ if line.startswith("group="):
+ elem = line.split("group=")
+ if len(elem[1]) > 0:
+ group = elem[1]
+ # Profile protocol (for different icons)
+ if line.startswith("protocol="):
+ elem = line.split("protocol=")
+ if len(elem[1]) > 0:
+ proto = elem[1].strip().lower()
+ else:
+ pass
+ if len(username) > 0:
+ server = "{username}@{server}".format(username=username,
+ server=server)
+ if len(proto) > 0:
+ server = "{proto}://{server}".format(proto=proto,
+ server=server)
+ if len(group) > 0:
+ group = " | {group}".format(group=group)
+ desc = "{name} | {server} {group}".format(name=name,
+ server=server,
+ group=group)
+ return desc, proto
+ else:
+ # Default values
+ return "", "rdp"
+
+
+if __name__ == '__main__':
+ RemminaExtension().run()
diff --git a/manifest.json b/manifest.json
new file mode 100644
index 0000000..8a95332
--- /dev/null
+++ b/manifest.json
@@ -0,0 +1,20 @@
+{
+ "manifest_version": "1",
+ "api_version": "1",
+ "name": "Remmina",
+ "description": "Quick access to Remmina profiles",
+ "developer_name": "noam09",
+ "icon": "images/icon.svg",
+ "options": {
+ "query_debounce": 0.1
+ },
+ "preferences": [
+ {
+ "id": "remmina",
+ "type": "keyword",
+ "name": "Remmina",
+ "description": "Quick access to Remmina profiles",
+ "default_value": "r"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/screenshot.png b/screenshot.png
new file mode 100644
index 0000000..f42f63e
Binary files /dev/null and b/screenshot.png differ