Noam 6 lat temu
rodzic
commit
efc32d9935
8 zmienionych plików z 361 dodań i 0 usunięć
  1. 2 0
      .gitignore
  2. 9 0
      README.md
  3. 148 0
      images/icon.svg
  4. 41 0
      images/rdp.svg
  5. 0 0
      images/ssh.svg
  6. 141 0
      main.py
  7. 20 0
      manifest.json
  8. BIN
      screenshot.png

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+*tmp*
+*.gvd*

+ 9 - 0
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).
+
+![ulauncher-remmina extension screenshot](screenshot.png)

+ 148 - 0
images/icon.svg

@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="444.66092"
+   height="444.66092"
+   viewBox="0 0 117.64988 117.64986"
+   version="1.1"
+   id="svg8"
+   inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
+   sodipodi:docname="icon_color.svg"
+   inkscape:export-filename="/home/anxhelo/works/Remmina/PNG/icon_color.png"
+   inkscape:export-xdpi="96"
+   inkscape:export-ydpi="96">
+  <defs
+     id="defs2" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="0.52467208"
+     inkscape:cx="102.72298"
+     inkscape:cy="224.46532"
+     inkscape:document-units="px"
+     inkscape:current-layer="g875"
+     inkscape:document-rotation="0"
+     showgrid="false"
+     borderlayer="true"
+     inkscape:showpageshadow="true"
+     units="px"
+     inkscape:pagecheckerboard="false"
+     showguides="true"
+     inkscape:window-width="1366"
+     inkscape:window-height="715"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     objecttolerance="10"
+     guidetolerance="10"
+     inkscape:snap-tangential="true"
+     inkscape:snap-perpendicular="true"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0">
+    <inkscape:grid
+       type="xygrid"
+       id="grid10"
+       dotted="false"
+       originx="-75.247744"
+       originy="-8.9404304" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata5">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+        <cc:license
+           rdf:resource="" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-75.247731,-170.4097)">
+    <g
+       id="g875"
+       transform="matrix(0.64508605,0,0,0.64508605,73.187616,170.4097)">
+      <g
+         id="g1019"
+         transform="matrix(2.3733304,0,0,2.3733304,3.19355,0)">
+        <g
+           id="g863">
+          <g
+             id="g855">
+            <polygon
+               style="fill:#20aa73"
+               class="st1"
+               points="20.618,17.742 20.618,26.923 29.549,32.877 20.618,38.831 20.618,48.012 43.32,32.877 "
+               id="polygon853" />
+          </g>
+          <g
+             id="g861">
+            <g
+               id="g859">
+              <polygon
+                 style="fill:#4a93dd"
+                 class="st2"
+                 points="55.388,59.232 55.388,50.051 46.457,44.097 55.388,38.143 55.388,28.962 32.685,44.097 "
+                 id="polygon857" />
+            </g>
+          </g>
+        </g>
+        <g
+           id="g873">
+          <g
+             id="g867">
+            <path
+               style="fill:#20aa73"
+               inkscape:connector-curvature="0"
+               class="st1"
+               d="m 64.776,23.074 c 2.632,4.509 4.147,9.75 4.147,15.348 0,16.845 -13.656,30.5 -30.5,30.5 -9.922,0 -18.734,-4.741 -24.304,-12.077 L 7.53,61.238 c 7.005,9.461 18.243,15.607 30.892,15.607 21.186,0 38.423,-17.236 38.423,-38.423 0,-7.215 -2.002,-13.97 -5.476,-19.744 z"
+               id="path865" />
+          </g>
+          <g
+             id="g871">
+            <path
+               style="fill:#4a93dd"
+               inkscape:connector-curvature="0"
+               class="st2"
+               d="m 7.922,38.423 c 0,-16.845 13.656,-30.5 30.5,-30.5 9.826,0 18.561,4.652 24.139,11.868 l 6.596,-4.397 C 62.142,6.053 50.976,0 38.423,0 17.236,0 0,17.236 0,38.423 0,45.543 1.954,52.212 5.343,57.936 L 11.937,53.54 C 9.387,49.083 7.922,43.925 7.922,38.423 Z"
+               id="path869" />
+          </g>
+        </g>
+      </g>
+    </g>
+  </g>
+  <style
+     id="style835"
+     type="text/css">
+	.st0{fill:#333333;}
+	.st1{fill:#20AA73;}
+	.st2{fill:#4A93DD;}
+</style>
+  <style
+     id="style905"
+     type="text/css">
+	.st0{fill:#333333;}
+	.st1{fill:#20AA73;}
+	.st2{fill:#4A93DD;}
+</style>
+</svg>

+ 41 - 0
images/rdp.svg

@@ -0,0 +1,41 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
+ <defs>
+  <linearGradient id="linearGradient3764" x1="1" x2="47" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0,-1,1,0,-1.5e-6,47.999998)">
+   <stop style="stop-color:#e4e4e4;stop-opacity:1"/>
+   <stop offset="1" style="stop-color:#eee;stop-opacity:1"/>
+  </linearGradient>
+ </defs>
+ <g>
+  <path d="m 36.31 5 c 5.859 4.062 9.688 10.831 9.688 18.5 c 0 12.426 -10.07 22.5 -22.5 22.5 c -7.669 0 -14.438 -3.828 -18.5 -9.688 c 1.037 1.822 2.306 3.499 3.781 4.969 c 4.085 3.712 9.514 5.969 15.469 5.969 c 12.703 0 23 -10.298 23 -23 c 0 -5.954 -2.256 -11.384 -5.969 -15.469 c -1.469 -1.475 -3.147 -2.744 -4.969 -3.781 z m 4.969 3.781 c 3.854 4.113 6.219 9.637 6.219 15.719 c 0 12.703 -10.297 23 -23 23 c -6.081 0 -11.606 -2.364 -15.719 -6.219 c 4.16 4.144 9.883 6.719 16.219 6.719 c 12.703 0 23 -10.298 23 -23 c 0 -6.335 -2.575 -12.06 -6.719 -16.219 z" style="opacity:0.05"/>
+  <path d="m 41.28 8.781 c 3.712 4.085 5.969 9.514 5.969 15.469 c 0 12.703 -10.297 23 -23 23 c -5.954 0 -11.384 -2.256 -15.469 -5.969 c 4.113 3.854 9.637 6.219 15.719 6.219 c 12.703 0 23 -10.298 23 -23 c 0 -6.081 -2.364 -11.606 -6.219 -15.719 z" style="opacity:0.1"/>
+  <path d="m 31.25 2.375 c 8.615 3.154 14.75 11.417 14.75 21.13 c 0 12.426 -10.07 22.5 -22.5 22.5 c -9.708 0 -17.971 -6.135 -21.12 -14.75 a 23 23 0 0 0 44.875 -7 a 23 23 0 0 0 -16 -21.875 z" style="opacity:0.2"/>
+ </g>
+ <g>
+  <path d="m 24 1 c 12.703 0 23 10.297 23 23 c 0 12.703 -10.297 23 -23 23 -12.703 0 -23 -10.297 -23 -23 0 -12.703 10.297 -23 23 -23 z" style="fill:url(#linearGradient3764);fill-opacity:1"/>
+ </g>
+ <g>
+  <g>
+   <g transform="translate(1,1)">
+    <g style="opacity:0.1">
+     <!-- color: #eeeeee -->
+     <g>
+      <path d="m 11.531 13 c -0.297 0.031 -0.531 0.297 -0.531 0.594 l 0 10.816 c 0 0.316 0.277 0.594 0.594 0.594 l 5.406 0 l 0 2 l -4 0 l 0 2 l 7.281 0 l 0 -6 l -6.84 0 c -0.211 0 -0.41 -0.195 -0.41 -0.41 l 0 -7.184 c -0.004 -0.188 0.129 -0.371 0.313 -0.406 c 0.023 -0.004 0.066 -0.004 0.094 0 l 11.125 0 c 0.207 0 0.406 0.191 0.406 0.406 l 0 2.875 l 2.031 0 l 0 -4.691 c 0 -0.313 -0.281 -0.594 -0.594 -0.594 m -14.875 0" style="fill:#000;fill-opacity:1;stroke:none;fill-rule:nonzero"/>
+      <path d="m 21.535 19 c -0.293 0.031 -0.539 0.305 -0.539 0.598 l 0 10.809 c 0 0.313 0.289 0.598 0.602 0.598 l 5.398 0 l 0 2.992 l 4 0 l 0 -2.992 l 5.402 0 c 0.313 0 0.598 -0.285 0.598 -0.598 l 0 -10.809 c 0 -0.316 -0.289 -0.598 -0.598 -0.598 m -13.05 2 c 0.023 -0.004 0.047 -0.004 0.074 0 l 11.145 0 c 0.211 0 0.402 0.191 0.402 0.402 l 0 7.203 c 0 0.207 -0.191 0.398 -0.402 0.398 l -11.145 0 c -0.211 0 -0.402 -0.191 -0.402 -0.398 l 0 -7.203 c -0.004 -0.188 0.145 -0.367 0.328 -0.402 m -0.352 12 l 0 2 l 12 0 l 0 -2 m -12 0" style="fill:#000;fill-opacity:1;stroke:none;fill-rule:nonzero"/>
+     </g>
+    </g>
+   </g>
+  </g>
+ </g>
+ <g>
+  <g>
+   <!-- color: #eeeeee -->
+   <g>
+    <path d="m 11.531 13 c -0.297 0.031 -0.531 0.297 -0.531 0.594 l 0 10.816 c 0 0.316 0.277 0.594 0.594 0.594 l 5.406 0 l 0 2 l -4 0 l 0 2 l 7.281 0 l 0 -6 l -6.84 0 c -0.211 0 -0.41 -0.195 -0.41 -0.41 l 0 -7.184 c -0.004 -0.188 0.129 -0.371 0.313 -0.406 c 0.023 -0.004 0.066 -0.004 0.094 0 l 11.125 0 c 0.207 0 0.406 0.191 0.406 0.406 l 0 2.875 l 2.031 0 l 0 -4.691 c 0 -0.313 -0.281 -0.594 -0.594 -0.594 m -14.875 0" style="fill:#c0274b;fill-opacity:1;stroke:none;fill-rule:nonzero"/>
+    <path d="m 21.535 19 c -0.293 0.031 -0.539 0.305 -0.539 0.598 l 0 10.809 c 0 0.313 0.289 0.598 0.602 0.598 l 5.398 0 l 0 2.992 l 4 0 l 0 -2.992 l 5.402 0 c 0.313 0 0.598 -0.285 0.598 -0.598 l 0 -10.809 c 0 -0.316 -0.289 -0.598 -0.598 -0.598 m -13.05 2 c 0.023 -0.004 0.047 -0.004 0.074 0 l 11.145 0 c 0.211 0 0.402 0.191 0.402 0.402 l 0 7.203 c 0 0.207 -0.191 0.398 -0.402 0.398 l -11.145 0 c -0.211 0 -0.402 -0.191 -0.402 -0.398 l 0 -7.203 c -0.004 -0.188 0.145 -0.367 0.328 -0.402 m -0.352 12 l 0 2 l 12 0 l 0 -2 m -12 0" style="fill:#283751;fill-opacity:1;stroke:none;fill-rule:nonzero"/>
+   </g>
+  </g>
+ </g>
+ <g>
+  <path d="m 40.03 7.531 c 3.712 4.084 5.969 9.514 5.969 15.469 0 12.703 -10.297 23 -23 23 c -5.954 0 -11.384 -2.256 -15.469 -5.969 4.178 4.291 10.01 6.969 16.469 6.969 c 12.703 0 23 -10.298 23 -23 0 -6.462 -2.677 -12.291 -6.969 -16.469 z" style="opacity:0.1"/>
+ </g>
+</svg>

Plik diff jest za duży
+ 0 - 0
images/ssh.svg


+ 141 - 0
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()

+ 20 - 0
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"
+    }
+  ]
+}

BIN
screenshot.png


Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików