client/systray: add installer for a freedesktop autostart file (#18767)
Adds freedesktop as an option for installing autostart desktop files for starting the systray application. Fixes #18766 Signed-off-by: Claus Lensbøl <claus@tailscale.com>main
parent
da90ea664d
commit
5ac35b665b
@ -0,0 +1,43 @@ |
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package freedesktop provides helpers for freedesktop systems.
|
||||
package freedesktop |
||||
|
||||
import "strings" |
||||
|
||||
const needsEscape = " \t\n\"'\\><~|&;$*?#()`" |
||||
|
||||
var escaper = strings.NewReplacer(`"`, `\"`, "`", "\\`", `$`, `\$`, `\`, `\\`) |
||||
|
||||
// Quote quotes according to the Desktop Entry Specification, as below:
|
||||
//
|
||||
// Arguments may be quoted in whole. If an argument contains a reserved
|
||||
// character the argument must be quoted. The rules for quoting of arguments is
|
||||
// also applicable to the executable name or path of the executable program as
|
||||
// provided.
|
||||
//
|
||||
// Quoting must be done by enclosing the argument between double quotes and
|
||||
// escaping the double quote character, backtick character ("`"), dollar sign
|
||||
// ("$") and backslash character ("\") by preceding it with an additional
|
||||
// backslash character. Implementations must undo quoting before expanding field
|
||||
// codes and before passing the argument to the executable program. Reserved
|
||||
// characters are space (" "), tab, newline, double quote, single quote ("'"),
|
||||
// backslash character ("\"), greater-than sign (">"), less-than sign ("<"),
|
||||
// tilde ("~"), vertical bar ("|"), ampersand ("&"), semicolon (";"), dollar
|
||||
// sign ("$"), asterisk ("*"), question mark ("?"), hash mark ("#"), parenthesis
|
||||
// ("(") and (")") and backtick character ("`").
|
||||
func Quote(s string) string { |
||||
if s == "" { |
||||
return `""` |
||||
} |
||||
if !strings.ContainsAny(s, needsEscape) { |
||||
return s |
||||
} |
||||
|
||||
var b strings.Builder |
||||
b.WriteString(`"`) |
||||
escaper.WriteString(&b, s) |
||||
b.WriteString(`"`) |
||||
return b.String() |
||||
} |
||||
@ -0,0 +1,145 @@ |
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package freedesktop |
||||
|
||||
import ( |
||||
"strings" |
||||
"testing" |
||||
) |
||||
|
||||
func TestEscape(t *testing.T) { |
||||
tests := []struct { |
||||
name, input, want string |
||||
}{ |
||||
{ |
||||
name: "no illegal chars", |
||||
input: "/home/user", |
||||
want: "/home/user", |
||||
}, |
||||
{ |
||||
name: "empty string", |
||||
input: "", |
||||
want: "\"\"", |
||||
}, |
||||
{ |
||||
name: "space", |
||||
input: " ", |
||||
want: "\" \"", |
||||
}, |
||||
{ |
||||
name: "tab", |
||||
input: "\t", |
||||
want: "\"\t\"", |
||||
}, |
||||
{ |
||||
name: "newline", |
||||
input: "\n", |
||||
want: "\"\n\"", |
||||
}, |
||||
{ |
||||
name: "double quote", |
||||
input: "\"", |
||||
want: "\"\\\"\"", |
||||
}, |
||||
{ |
||||
name: "single quote", |
||||
input: "'", |
||||
want: "\"'\"", |
||||
}, |
||||
{ |
||||
name: "backslash", |
||||
input: "\\", |
||||
want: "\"\\\\\"", |
||||
}, |
||||
{ |
||||
name: "greater than", |
||||
input: ">", |
||||
want: "\">\"", |
||||
}, |
||||
{ |
||||
name: "less than", |
||||
input: "<", |
||||
want: "\"<\"", |
||||
}, |
||||
{ |
||||
name: "tilde", |
||||
input: "~", |
||||
want: "\"~\"", |
||||
}, |
||||
{ |
||||
name: "pipe", |
||||
input: "|", |
||||
want: "\"|\"", |
||||
}, |
||||
{ |
||||
name: "ampersand", |
||||
input: "&", |
||||
want: "\"&\"", |
||||
}, |
||||
{ |
||||
name: "semicolon", |
||||
input: ";", |
||||
want: "\";\"", |
||||
}, |
||||
{ |
||||
name: "dollar", |
||||
input: "$", |
||||
want: "\"\\$\"", |
||||
}, |
||||
{ |
||||
name: "asterisk", |
||||
input: "*", |
||||
want: "\"*\"", |
||||
}, |
||||
{ |
||||
name: "question mark", |
||||
input: "?", |
||||
want: "\"?\"", |
||||
}, |
||||
{ |
||||
name: "hash", |
||||
input: "#", |
||||
want: "\"#\"", |
||||
}, |
||||
{ |
||||
name: "open paren", |
||||
input: "(", |
||||
want: "\"(\"", |
||||
}, |
||||
{ |
||||
name: "close paren", |
||||
input: ")", |
||||
want: "\")\"", |
||||
}, |
||||
{ |
||||
name: "backtick", |
||||
input: "`", |
||||
want: "\"\\`\"", |
||||
}, |
||||
{ |
||||
name: "char without escape", |
||||
input: "/home/user\t", |
||||
want: "\"/home/user\t\"", |
||||
}, |
||||
{ |
||||
name: "char with escape", |
||||
input: "/home/user\\", |
||||
want: "\"/home/user\\\\\"", |
||||
}, |
||||
{ |
||||
name: "all illegal chars", |
||||
input: "/home/user" + needsEscape, |
||||
want: "\"/home/user \t\n\\\"'\\\\><~|&;\\$*?#()\\`\"", |
||||
}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
got := Quote(tt.input) |
||||
if strings.Compare(got, tt.want) != 0 { |
||||
t.Errorf("expected %s, got %s", tt.want, got) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
@ -0,0 +1,13 @@ |
||||
[Desktop Entry] |
||||
Type=Application |
||||
Version=1.0 |
||||
Name=Tailscale System Tray |
||||
Comment=Tailscale system tray applet for managing Tailscale |
||||
Exec=/usr/bin/tailscale systray |
||||
TryExec=/usr/bin/tailscale |
||||
Terminal=false |
||||
NoDisplay=true |
||||
StartupNotify=false |
||||
Icon=tailscale |
||||
Categories=Network;System; |
||||
X-GNOME-Autostart-enabled=true |
||||
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
Loading…
Reference in new issue