This commit is contained in:
Noam
2018-09-02 18:51:19 +03:00
parent 4a4e4c58d0
commit efc32d9935
8 changed files with 362 additions and 0 deletions

2
.gitignore vendored Normal file
View File

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

9
README.md Normal file
View File

@@ -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
images/icon.svg Normal file
View File

@@ -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>

After

Width:  |  Height:  |  Size: 4.5 KiB

41
images/rdp.svg Normal file
View File

@@ -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>

After

Width:  |  Height:  |  Size: 4.2 KiB

1
images/ssh.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="isolation:isolate" viewBox="0 0 48 48" width="48" height="48"><defs><clipPath id="_clipPath_arE3vWAN7RkXd8FTLNLuqo1WJ5r2YolW"><rect width="48" height="48"/></clipPath></defs><g clip-path="url(#_clipPath_arE3vWAN7RkXd8FTLNLuqo1WJ5r2YolW)"><path d=" M 0.606 24 C 0.606 11.089 11.089 0.606 24 0.606 C 36.911 0.606 47.394 11.089 47.394 24 C 47.394 36.911 36.911 47.394 24 47.394 C 11.089 47.394 0.606 36.911 0.606 24 Z " fill="rgb(46,46,46)"/><path d=" M 0 24 C -0.001 22.377 0.162 20.758 0.488 19.166 C 0.805 17.622 1.273 16.113 1.887 14.662 C 3.096 11.811 4.842 9.221 7.033 7.033 C 9.221 4.842 11.811 3.096 14.662 1.887 C 16.113 1.273 17.622 0.805 19.166 0.488 C 20.756 0.162 22.377 -0.001 24 0 C 25.623 -0.001 27.244 0.162 28.834 0.488 C 30.378 0.805 31.887 1.273 33.338 1.887 C 36.189 3.096 38.779 4.842 40.967 7.033 C 43.158 9.221 44.904 11.811 46.113 14.662 C 46.727 16.113 47.195 17.622 47.512 19.166 C 47.838 20.756 48.001 22.377 48 24 C 48.001 25.623 47.838 27.244 47.512 28.834 C 47.195 30.378 46.727 31.887 46.113 33.338 C 44.904 36.189 43.158 38.779 40.967 40.967 C 38.779 43.158 36.189 44.904 33.338 46.113 C 31.887 46.727 30.378 47.195 28.834 47.512 C 27.244 47.838 25.623 48.001 24 48 C 22.377 48.001 20.756 47.838 19.166 47.512 C 17.622 47.195 16.113 46.727 14.662 46.113 C 11.811 44.904 9.221 43.158 7.033 40.967 C 4.842 38.779 3.096 36.189 1.887 33.338 C 1.273 31.887 0.805 30.378 0.488 28.834 C 0.162 27.244 -0.001 25.623 0 24 Z M 1.212 24 C 1.211 22.458 1.367 20.92 1.675 19.41 C 1.976 17.944 2.421 16.512 3.004 15.135 C 4.152 12.428 5.81 9.967 7.89 7.89 C 9.967 5.81 12.428 4.152 15.135 3.004 C 16.512 2.421 17.944 1.976 19.41 1.675 C 20.92 1.367 22.458 1.211 24 1.212 C 25.542 1.211 27.08 1.367 28.59 1.675 C 30.056 1.976 31.488 2.421 32.865 3.004 C 35.572 4.152 38.033 5.81 40.11 7.89 C 42.19 9.967 43.848 12.428 44.996 15.135 C 45.579 16.512 46.024 17.944 46.325 19.41 C 46.633 20.92 46.789 22.458 46.788 24 C 46.789 25.542 46.633 27.08 46.325 28.59 C 46.024 30.056 45.579 31.488 44.996 32.865 C 43.848 35.572 42.19 38.033 40.11 40.11 C 38.033 42.19 35.572 43.848 32.865 44.996 C 31.488 45.579 30.056 46.024 28.59 46.325 C 27.08 46.633 25.542 46.789 24 46.788 C 22.458 46.789 20.92 46.633 19.41 46.325 C 17.944 46.024 16.512 45.579 15.135 44.996 C 12.428 43.848 9.967 42.19 7.89 40.11 C 5.81 38.033 4.152 35.572 3.004 32.865 C 2.421 31.488 1.976 30.056 1.675 28.59 C 1.367 27.08 1.211 25.542 1.212 24 Z " fill-rule="evenodd" fill="rgb(111,111,111)"/><path d=" M 18.735 21.13 L 10.579 18.444 L 10.579 14.622 L 23.367 19.823 L 23.367 23.048 L 10.579 28.263 L 10.579 24.426 L 18.721 21.698 L 19.588 21.414 L 18.735 21.13 Z M 37.421 30.295 L 37.421 33.378 L 24.462 33.378 L 24.462 30.295 L 37.421 30.295 Z " fill-rule="evenodd" fill="rgb(232,232,232)"/></g></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

141
main.py Normal file
View File

@@ -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
manifest.json Normal file
View File

@@ -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 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB