diff options
Diffstat (limited to 'kvmd')
-rw-r--r-- | kvmd/configs/kvmd/v1.yaml | 7 | ||||
-rw-r--r-- | kvmd/kvmd/__init__.py | 3 | ||||
-rw-r--r-- | kvmd/kvmd/server.py | 21 | ||||
-rw-r--r-- | kvmd/kvmd/streamer.py | 34 | ||||
-rw-r--r-- | kvmd/testenv/kvmd.yaml | 9 | ||||
-rw-r--r-- | kvmd/web/css/main.css | 36 | ||||
-rw-r--r-- | kvmd/web/css/stream.css | 23 | ||||
-rw-r--r-- | kvmd/web/index.html | 11 | ||||
-rw-r--r-- | kvmd/web/js/stream.js | 46 | ||||
-rw-r--r-- | kvmd/web/svg/select-arrow-inactive.svg | 78 | ||||
-rw-r--r-- | kvmd/web/svg/select-arrow-intensive.svg | 78 | ||||
-rw-r--r-- | kvmd/web/svg/select-arrow-normal.svg | 78 |
12 files changed, 383 insertions, 41 deletions
diff --git a/kvmd/configs/kvmd/v1.yaml b/kvmd/configs/kvmd/v1.yaml index 5d1fc783..712891a0 100644 --- a/kvmd/configs/kvmd/v1.yaml +++ b/kvmd/configs/kvmd/v1.yaml @@ -36,14 +36,13 @@ kvmd: init_restart_after: 1.0 shutdown_delay: 10.0 - size: - width: 800 - height: 600 + resolutions: + - 800x600 - 720x576 cmd: - "/usr/bin/mjpg_streamer" - "-i" - - "input_uvc.so -d /dev/kvmd-streamer -e 2 -t pal -y -n -r 720x576" + - "input_uvc.so -d /dev/kvmd-streamer -e 2 -t pal -y -n -r {resolution}" - "-o" - "output_http.so -l localhost -p 8082" diff --git a/kvmd/kvmd/__init__.py b/kvmd/kvmd/__init__.py index c1e3dc3d..40eead3b 100644 --- a/kvmd/kvmd/__init__.py +++ b/kvmd/kvmd/__init__.py @@ -49,8 +49,7 @@ def main() -> None: sync_delay=float(config["streamer"]["sync_delay"]), init_delay=float(config["streamer"]["init_delay"]), init_restart_after=float(config["streamer"]["init_restart_after"]), - width=int(config["streamer"]["size"]["width"]), - height=int(config["streamer"]["size"]["height"]), + resolutions=config["streamer"]["resolutions"], cmd=list(map(str, config["streamer"]["cmd"])), loop=loop, ) diff --git a/kvmd/kvmd/server.py b/kvmd/kvmd/server.py index cc5fdbf0..0f76808f 100644 --- a/kvmd/kvmd/server.py +++ b/kvmd/kvmd/server.py @@ -128,6 +128,7 @@ class Server: # pylint: disable=too-many-instance-attributes self.__system_tasks: List[asyncio.Task] = [] self.__reset_streamer = False + self.__streamer_resolution = streamer.get_current_resolution() def run(self, host: str, port: int) -> None: self.__hid.start() @@ -148,6 +149,7 @@ class Server: # pylint: disable=too-many-instance-attributes app.router.add_post("/msd/write", self.__msd_write_handler) app.router.add_get("/streamer", self.__streamer_state_handler) + app.router.add_post("/streamer/set_params", self.__streamer_set_params_handler) app.router.add_post("/streamer/reset", self.__streamer_reset_handler) app.on_shutdown.append(self.__on_shutdown) @@ -301,6 +303,18 @@ class Server: # pylint: disable=too-many-instance-attributes async def __streamer_state_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response: return _json(self.__streamer.get_state()) + @_wrap_exceptions_for_web("Can't set stream params") + async def __streamer_set_params_handler(self, request: aiohttp.web.Request) -> aiohttp.web.Response: + resolution = request.query.get("resolution") + if resolution: + if resolution in self.__streamer.get_available_resolutions(): + if resolution != self.__streamer_resolution: + self.__streamer_resolution = resolution + self.__reset_streamer = True + else: + raise BadRequest("Unknown resolution %r" % (resolution)) + return _json() + async def __streamer_reset_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response: self.__reset_streamer = True return _json() @@ -344,17 +358,20 @@ class Server: # pylint: disable=too-many-instance-attributes cur = len(self.__sockets) if prev == 0 and cur > 0: if not self.__streamer.is_running(): - await self.__streamer.start() + await self.__streamer.start(self.__streamer_resolution) + await self.__broadcast_event("streamer_state", **self.__streamer.get_state()) elif prev > 0 and cur == 0: shutdown_at = time.time() + self.__streamer_shutdown_delay elif prev == 0 and cur == 0 and time.time() > shutdown_at: if self.__streamer.is_running(): await self.__streamer.stop() + await self.__broadcast_event("streamer_state", **self.__streamer.get_state()) if self.__reset_streamer: if self.__streamer.is_running(): await self.__streamer.stop() - await self.__streamer.start(no_init_restart=True) + await self.__streamer.start(self.__streamer_resolution, no_init_restart=True) + await self.__broadcast_event("streamer_state", **self.__streamer.get_state()) self.__reset_streamer = False prev = cur diff --git a/kvmd/kvmd/streamer.py b/kvmd/kvmd/streamer.py index 32cc60ab..7e3fd513 100644 --- a/kvmd/kvmd/streamer.py +++ b/kvmd/kvmd/streamer.py @@ -1,6 +1,8 @@ import asyncio import asyncio.subprocess +from collections import OrderedDict as odict + from typing import List from typing import Dict from typing import Optional @@ -20,8 +22,7 @@ class Streamer: # pylint: disable=too-many-instance-attributes init_delay: float, init_restart_after: float, - width: int, - height: int, + resolutions: List[str], cmd: List[str], loop: asyncio.AbstractEventLoop, @@ -33,17 +34,25 @@ class Streamer: # pylint: disable=too-many-instance-attributes self.__init_delay = init_delay self.__init_restart_after = init_restart_after - self.__width = width - self.__height = height + self.__resolutions = odict([ + (display, (real or display)) + for (display, real) in [ + (tuple(map(str.lower, map(str.strip, resolution.split("-", maxsplit=1)))) + ("",))[:2] + for resolution in resolutions + ] + ]) + self.__resolution = list(self.__resolutions)[0] self.__cmd = cmd self.__loop = loop self.__proc_task: Optional[asyncio.Task] = None - async def start(self, no_init_restart: bool=False) -> None: + async def start(self, resolution: str, no_init_restart: bool=False) -> None: logger = get_logger() logger.info("Starting streamer ...") + assert resolution in self.__resolutions, (resolution, self.__resolutions) + self.__resolution = resolution await self.__inner_start() if self.__init_restart_after > 0.0 and not no_init_restart: logger.info("Stopping streamer to restart ...") @@ -58,13 +67,22 @@ class Streamer: # pylint: disable=too-many-instance-attributes def is_running(self) -> bool: return bool(self.__proc_task) + def get_current_resolution(self) -> str: + return self.__resolution + + def get_available_resolutions(self) -> List[str]: + return list(self.__resolutions) + def get_state(self) -> Dict: + (width, height) = tuple(map(int, self.__resolution.split("x"))) return { "is_running": self.is_running(), "size": { - "width": self.__width, - "height": self.__height, + "width": width, + "height": height, }, + "resolution": self.__resolution, + "resolutions": list(self.__resolutions), } async def cleanup(self) -> None: @@ -100,7 +118,7 @@ class Streamer: # pylint: disable=too-many-instance-attributes while True: # pylint: disable=too-many-nested-blocks proc: Optional[asyncio.subprocess.Process] = None # pylint: disable=no-member try: - cmd = [part.format(width=self.__width, height=self.__height) for part in self.__cmd] + cmd = [part.format(resolution=self.__resolutions[self.__resolution]) for part in self.__cmd] proc = await asyncio.create_subprocess_exec( *cmd, stdout=asyncio.subprocess.PIPE, diff --git a/kvmd/testenv/kvmd.yaml b/kvmd/testenv/kvmd.yaml index e1773847..1ad9e7f5 100644 --- a/kvmd/testenv/kvmd.yaml +++ b/kvmd/testenv/kvmd.yaml @@ -36,14 +36,15 @@ kvmd: init_restart_after: 1.0 shutdown_delay: 10.0 - size: - width: 800 - height: 600 + resolutions: + - 640x480 + - 800x600 + - 1024x768 cmd: - "/usr/bin/mjpg_streamer" - "-i" - - "input_uvc.so -d /dev/kvmd-streamer -e 2 -y -n -r {width}x{height}" + - "input_uvc.so -d /dev/kvmd-streamer -e 2 -y -n -r {resolution}" - "-o" - "output_http.so -l 0.0.0.0 -p 8082" diff --git a/kvmd/web/css/main.css b/kvmd/web/css/main.css index 44ca1ac1..4ff39abe 100644 --- a/kvmd/web/css/main.css +++ b/kvmd/web/css/main.css @@ -91,7 +91,7 @@ div.ctl-dropdown-content div.buttons-row { padding: 0; font-size: 0; } -div.ctl-dropdown-content button { +div.ctl-dropdown-content button, select { box-shadow: none; border: none; color: var(--fg-color-normal); @@ -105,26 +105,48 @@ div.ctl-dropdown-content button { outline: none; cursor: pointer; } -div.ctl-dropdown-content button:enabled:hover { +div.ctl-dropdown-content button:enabled:hover, select:enabled:hover { color: var(--fg-color-intensive); background-color: var(--bg-color-dark) !important; } -div.ctl-dropdown-content button:disabled { +div.ctl-dropdown-content button:disabled, select:disabled { color: var(--fg-color-inactive); cursor: default; } -div.ctl-dropdown-content button:active { +div.ctl-dropdown-content button:active, select:active { color: var(--fg-color-selected) !important; } -div.ctl-dropdown-content button.row50 { +div.ctl-dropdown-content select { + -webkit-appearance: button; + -moz-appearance: button; + appearance: button; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + background-image: url("../svg/select-arrow-normal.svg"); + background-position: center right; + background-repeat: no-repeat; +} +div.ctl-dropdown-content select:enabled:hover { + background-image: url("../svg/select-arrow-intensive.svg") !important; +} +div.ctl-dropdown-content select:disabled { + background-image: url("../svg/select-arrow-inactive.svg") !important; +} +div.ctl-dropdown-content select:active { + color: var(--fg-color-intensive) !important; + background-color: var(--bg-color-dark) !important; + background-image: url("../svg/select-arrow-intensive.svg") !important; +} +div.ctl-dropdown-content .row50 { display: inline-block; width: 50%; } -div.ctl-dropdown-content button.row25 { +div.ctl-dropdown-content .row25 { display: inline-block; width: 25%; } -div.ctl-dropdown-content button.row50:not(:first-child), button.row25:not(:first-child) { +div.ctl-dropdown-content .row50:not(:first-child), .row25:not(:first-child) { border-left: var(--dark-border); } div.ctl-dropdown-content hr { diff --git a/kvmd/web/css/stream.css b/kvmd/web/css/stream.css index c3429630..01fd3cc4 100644 --- a/kvmd/web/css/stream.css +++ b/kvmd/web/css/stream.css @@ -35,21 +35,26 @@ div.stream-box-mouse-enabled { cursor: url("../svg/stream-mouse-cursor.svg"), pointer; } -div#stream-size { +div.stream-params { -webkit-user-select: text; -moz-user-select: text; user-select: text; font-size: 12px; margin: 5px 15px 5px 15px; } -div#stream-size span#stream-size-counter { + +div.stream-params select#stream-resolution-select { + margin: 8px 0 8px 0; +} + +div.stream-params span#stream-size-value { } -div#stream-size div#stream-size-slider-box { +div.stream-params div#stream-size-slider-box { margin-top: 5px; display: flex; } @supports (-webkit-appearance:none) { - div#stream-size div#stream-size-slider-box input[type=range] { + div.stream-params div#stream-size-slider-box input[type=range] { cursor: pointer; outline: none; width: 100%; @@ -60,7 +65,7 @@ div#stream-size div#stream-size-slider-box { } } @supports not (-webkit-appearance:none) { - div#stream-size div#stream-size-slider-box input[type=range] { + div.stream-params div#stream-size-slider-box input[type=range] { cursor: pointer; outline: none; width: 100%; @@ -69,12 +74,12 @@ div#stream-size div#stream-size-slider-box { margin-right: 0; } } -div#stream-size div#stream-size-slider-box input[type=range]::-webkit-slider-runnable-track { +div.stream-params div#stream-size-slider-box input[type=range]::-webkit-slider-runnable-track { height: 5px; background: var(--bg-color-light); border-radius: 3px; } -div#stream-size div#stream-size-slider-box input[type=range]::-webkit-slider-thumb { +div.stream-params div#stream-size-slider-box input[type=range]::-webkit-slider-thumb { border: var(--intensive-border); height: 18px; width: 18px; @@ -83,12 +88,12 @@ div#stream-size div#stream-size-slider-box input[type=range]::-webkit-slider-thu -webkit-appearance: none; margin-top: -7px; } -div#stream-size div#stream-size-slider-box input[type=range]::-moz-range-track { +div.stream-params div#stream-size-slider-box input[type=range]::-moz-range-track { height: 5px; background: var(--bg-color-light); border-radius: 3px; } -div#stream-size div#stream-size-slider-box input[type=range]::-moz-range-thumb { +div.stream-params div#stream-size-slider-box input[type=range]::-moz-range-thumb { border: var(--intensive-border); height: 18px; width: 18px; diff --git a/kvmd/web/index.html b/kvmd/web/index.html index adc024db..8ae5d250 100644 --- a/kvmd/web/index.html +++ b/kvmd/web/index.html @@ -70,8 +70,15 @@ <button id="show-stream-button">• Show stream</button> <button disabled id="stream-reset-button">• Reset stream</button> <hr> - <div data-dont-hide-menu id="stream-size"> - Stream size: <span id="stream-size-counter">100%</span> + <div data-dont-hide-menu class="stream-params"> + Resolution: + <select disabled data-dont-hide-menu id="stream-resolution-select"> + <option>640x480</option> + </select> + </div> + <hr> + <div data-dont-hide-menu class="stream-params"> + Stream size: <span id="stream-size-value">100%</span> <div id="stream-size-slider-box"> <input id="stream-size-slider" type="range" min="50" max="150" value="100" step="10" /> </div> diff --git a/kvmd/web/js/stream.js b/kvmd/web/js/stream.js index 8b6fe262..7eb09ee0 100644 --- a/kvmd/web/js/stream.js +++ b/kvmd/web/js/stream.js @@ -4,6 +4,10 @@ function Stream(ui) { /********************************************************************************/ var __prev_state = false; + + var __resolution = "640x480"; + var __resolutions = ["640x480"]; + var __normal_size = {width: 640, height: 480}; var __size_factor = 1; @@ -11,6 +15,7 @@ function Stream(ui) { $("stream-led").title = "Stream inactive"; $("stream-reset-button").onclick = __clickResetButton; + $("stream-resolution-select").onchange = __changeResolution; $("stream-size-slider").oninput = __resize; $("stream-size-slider").onchange = __resize; @@ -19,6 +24,8 @@ function Stream(ui) { /********************************************************************************/ + // XXX: In current implementation we don't need this event because Stream() has own state poller + var __startPoller = function() { var http = tools.makeRequest("GET", "/streamer/?action=snapshot", function() { if (http.readyState === 2 || http.readyState === 4) { @@ -33,6 +40,7 @@ function Stream(ui) { $("stream-led").className = "led-off"; $("stream-led").title = "Stream inactive"; $("stream-reset-button").disabled = true; + $("stream-resolution-select").disabled = true; } else if (!__prev_state) { __refreshImage(); __prev_state = true; @@ -44,7 +52,7 @@ function Stream(ui) { } } }); - setTimeout(__startPoller, 2000); + setTimeout(__startPoller, 1500); }; var __clickResetButton = function() { @@ -58,9 +66,23 @@ function Stream(ui) { }); }; + var __changeResolution = function() { + var resolution = $("stream-resolution-select").value; + if (__resolution != resolution) { + $("stream-resolution-select").disabled = true; + var http = tools.makeRequest("POST", "/kvmd/streamer/set_params?resolution=" + resolution, function() { + if (http.readyState === 4) { + if (http.status !== 200) { + alert("Can't change stream:", http.responseText); + } + } + }); + } + }; + var __resize = function() { var percent = $("stream-size-slider").value; - $("stream-size-counter").innerHTML = percent + "%"; + $("stream-size-value").innerHTML = percent + "%"; __size_factor = percent / 100; __applySizeFactor(); }; @@ -75,7 +97,25 @@ function Stream(ui) { var __refreshImage = function() { var http = tools.makeRequest("GET", "/kvmd/streamer", function() { if (http.readyState === 4 && http.status === 200) { - __normal_size = JSON.parse(http.responseText).result.size; + var result = JSON.parse(http.responseText).result; + + if (__resolutions != result.resolutions) { + tools.info("Resolutions list changed:", result.resolutions); + $("stream-resolution-select").innerHTML = ""; + result.resolutions.forEach(function(resolution) { + $("stream-resolution-select").innerHTML += "<option value=\"" + resolution + "\">" + resolution + "</option>"; + }); + $("stream-resolution-select").disabled = (result.resolutions.length == 1); + __resolutions = result.resolutions; + } + + if (__resolution != result.resolution) { + tools.info("Resolution changed:", result.resolution); + document.querySelector("#stream-resolution-select [value=\"" + result.resolution + "\"]").selected = true; + __resolution = result.resolution; + } + + __normal_size = result.size; __applySizeFactor(); $("stream-image").src = "/streamer/?action=stream&time=" + new Date().getTime(); } diff --git a/kvmd/web/svg/select-arrow-inactive.svg b/kvmd/web/svg/select-arrow-inactive.svg new file mode 100644 index 00000000..ba68f05c --- /dev/null +++ b/kvmd/web/svg/select-arrow-inactive.svg @@ -0,0 +1,78 @@ +<?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="24" + height="31.999998" + viewBox="0 0 6.3500001 8.4666662" + version="1.1" + id="svg8" + inkscape:version="0.92.2 2405546, 2018-03-11" + sodipodi:docname="select-arrow-inactive.svg"> + <defs + id="defs2" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="11.2" + inkscape:cx="27.151934" + inkscape:cy="16.615415" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + showgrid="false" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:window-width="1920" + inkscape:window-height="1020" + inkscape:window-x="0" + inkscape:window-y="30" + inkscape:window-maximized="1" + units="px" /> + <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:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-8.8745959,-36.821965)"> + <path + sodipodi:type="star" + style="opacity:1;fill:#6c7481;fill-opacity:1;stroke:none;stroke-width:2.64583325;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:75.59055328;stroke-opacity:1;paint-order:normal" + id="path4749" + sodipodi:sides="3" + sodipodi:cx="12.049596" + sodipodi:cy="40.702518" + sodipodi:r1="1.411111" + sodipodi:r2="0.70555568" + sodipodi:arg1="1.5707963" + sodipodi:arg2="2.6179939" + inkscape:flatsided="false" + inkscape:rounded="0" + inkscape:randomized="0" + d="m 12.049596,42.113629 -0.611029,-1.058333 -0.611029,-1.058333 1.222058,0 1.222058,0 -0.611029,1.058333 z" + inkscape:transform-center-y="0.3527758" /> + </g> +</svg> diff --git a/kvmd/web/svg/select-arrow-intensive.svg b/kvmd/web/svg/select-arrow-intensive.svg new file mode 100644 index 00000000..3223f099 --- /dev/null +++ b/kvmd/web/svg/select-arrow-intensive.svg @@ -0,0 +1,78 @@ +<?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="24" + height="31.999998" + viewBox="0 0 6.3500001 8.4666662" + version="1.1" + id="svg8" + inkscape:version="0.92.2 2405546, 2018-03-11" + sodipodi:docname="select-arrow-intensive.svg"> + <defs + id="defs2" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="11.2" + inkscape:cx="27.151934" + inkscape:cy="16.615415" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + showgrid="false" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:window-width="1920" + inkscape:window-height="1020" + inkscape:window-x="0" + inkscape:window-y="30" + inkscape:window-maximized="1" + units="px" /> + <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:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-8.8745959,-36.821965)"> + <path + sodipodi:type="star" + style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.64583325;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:75.59055328;stroke-opacity:1;paint-order:normal" + id="path4749" + sodipodi:sides="3" + sodipodi:cx="12.049596" + sodipodi:cy="40.702518" + sodipodi:r1="1.411111" + sodipodi:r2="0.70555568" + sodipodi:arg1="1.5707963" + sodipodi:arg2="2.6179939" + inkscape:flatsided="false" + inkscape:rounded="0" + inkscape:randomized="0" + d="m 12.049596,42.113629 -0.611029,-1.058333 -0.611029,-1.058333 1.222058,0 1.222058,0 -0.611029,1.058333 z" + inkscape:transform-center-y="0.3527758" /> + </g> +</svg> diff --git a/kvmd/web/svg/select-arrow-normal.svg b/kvmd/web/svg/select-arrow-normal.svg new file mode 100644 index 00000000..174663ce --- /dev/null +++ b/kvmd/web/svg/select-arrow-normal.svg @@ -0,0 +1,78 @@ +<?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="24" + height="31.999998" + viewBox="0 0 6.3500001 8.4666662" + version="1.1" + id="svg8" + inkscape:version="0.92.2 2405546, 2018-03-11" + sodipodi:docname="select-arrow-normal.svg"> + <defs + id="defs2" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="11.2" + inkscape:cx="27.151934" + inkscape:cy="16.615415" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + showgrid="false" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:window-width="1920" + inkscape:window-height="1020" + inkscape:window-x="0" + inkscape:window-y="30" + inkscape:window-maximized="1" + units="px" /> + <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:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-8.8745959,-36.821965)"> + <path + sodipodi:type="star" + style="opacity:1;fill:#c3c3c3;fill-opacity:1;stroke:none;stroke-width:2.64583325;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:75.59055328;stroke-opacity:1;paint-order:normal" + id="path4749" + sodipodi:sides="3" + sodipodi:cx="12.049596" + sodipodi:cy="40.702518" + sodipodi:r1="1.411111" + sodipodi:r2="0.70555568" + sodipodi:arg1="1.5707963" + sodipodi:arg2="2.6179939" + inkscape:flatsided="false" + inkscape:rounded="0" + inkscape:randomized="0" + d="m 12.049596,42.113629 -0.611029,-1.058333 -0.611029,-1.058333 1.222058,0 1.222058,0 -0.611029,1.058333 z" + inkscape:transform-center-y="0.3527758" /> + </g> +</svg> |