~davidmhewitt/slingshot/fix-1665931

« back to all changes in this revision

Viewing changes to lib/synapse-plugins/command-plugin.vala

  • Committer: RabbitBot
  • Author(s): Fabio Zaramella
  • Date: 2017-01-31 00:02:45 UTC
  • mfrom: (724.1.3 code-style)
  • Revision ID: rabbitbot-20170131000245-ef6klohh85zvrib5
Synapse-core and synapse-plugins:
* Code style

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
/*
2
 
 * Copyright (C) 2010 Michal Hruby <michal.mhr@gmail.com>
3
 
 *
4
 
 * This program is free software; you can redistribute it and/or modify
5
 
 * it under the terms of the GNU General Public License as published by
6
 
 * the Free Software Foundation; either version 2 of the License, or
7
 
 * (at your option) any later version.
8
 
 *
9
 
 * This program is distributed in the hope that it will be useful,
10
 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 
 * GNU General Public License for more details.
13
 
 *
14
 
 * You should have received a copy of the GNU General Public License
15
 
 * along with this program; if not, write to the Free Software
16
 
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
17
 
 *
18
 
 * Authored by Michal Hruby <michal.mhr@gmail.com>
19
 
 *
20
 
 */
21
 
 
22
 
namespace Synapse
23
 
{
24
 
  public class CommandPlugin: Object, Activatable, ItemProvider
25
 
  {
26
 
    public bool enabled { get; set; default = true; }
27
 
 
28
 
    public void activate ()
29
 
    {
30
 
      
31
 
    }
32
 
 
33
 
    public void deactivate ()
34
 
    {
35
 
      
36
 
    }
37
 
 
38
 
    private class CommandObject: Object, Match, ApplicationMatch
39
 
    {
40
 
      // for Match interface
41
 
      public string title { get; construct set; }
42
 
      public string description { get; set; default = ""; }
43
 
      public string icon_name { get; construct set; default = ""; }
44
 
      public bool has_thumbnail { get; construct set; default = false; }
45
 
      public string thumbnail_path { get; construct set; }
46
 
      public MatchType match_type { get; construct set; }
47
 
 
48
 
      // for ApplicationMatch
49
 
      public AppInfo? app_info { get; set; default = null; }
50
 
      public bool needs_terminal { get; set; default = false; }
51
 
      public string? filename { get; construct set; default = null; }
52
 
      public string command { get; construct set; }
53
 
      
54
 
      public CommandObject (string cmd)
55
 
      {
56
 
        Object (title: _("Execute '%s'").printf (cmd), description: _ ("Run command"), command: cmd,
57
 
                icon_name: "application-x-executable",
58
 
                match_type: MatchType.APPLICATION,
59
 
                needs_terminal: cmd.has_prefix ("sudo "));
60
 
 
61
 
        try
62
 
        {
63
 
          app_info = AppInfo.create_from_commandline (cmd, null, 0);
64
 
        }
65
 
        catch (Error err)
66
 
        {
67
 
          warning ("%s", err.message);
68
 
        }
69
 
      }
70
 
    }
71
 
    
72
 
    static void register_plugin ()
73
 
    {
74
 
      DataSink.PluginRegistry.get_default ().register_plugin (
75
 
        typeof (CommandPlugin),
76
 
        "Command Search",
77
 
        _ ("Find and execute arbitrary commands."),
78
 
        "system-run",
79
 
        register_plugin
80
 
      );
81
 
    }
82
 
    
83
 
    static construct
84
 
    {
85
 
      register_plugin ();
86
 
    }
87
 
 
88
 
    private Gee.Set<string> past_commands;
89
 
    private Regex split_regex;
90
 
 
91
 
    construct
92
 
    {
93
 
      // TODO: load from configuration
94
 
      past_commands = new Gee.HashSet<string> ();
95
 
      try
96
 
      {
97
 
        split_regex = new Regex ("\\s+", RegexCompileFlags.OPTIMIZE);
98
 
      }
99
 
      catch (RegexError err)
100
 
      {
101
 
        critical ("%s", err.message);
102
 
      }
103
 
    }
104
 
    
105
 
    private CommandObject? create_co (string exec)
106
 
    {
107
 
      // ignore results that will be returned by DesktopFilePlugin
108
 
      // and at the same time look for hidden and no-display desktop files,
109
 
      // so we can display their info (title, comment, icon)
110
 
      var dfs = DesktopFileService.get_default ();
111
 
      var df_list = dfs.get_desktop_files_for_exec (exec);
112
 
      DesktopFileInfo? dfi = null;
113
 
      foreach (var df in df_list)
114
 
      {
115
 
        if (!df.is_hidden) return null; // will be handled by App plugin
116
 
        dfi = df;
117
 
      }
118
 
 
119
 
      var co = new CommandObject (exec);
120
 
      if (dfi != null)
121
 
      {
122
 
        co.title = dfi.name;
123
 
        if (dfi.comment != "") co.description = dfi.comment;
124
 
        if (dfi.icon_name != null && dfi.icon_name != "") co.icon_name = dfi.icon_name;
125
 
      }
126
 
 
127
 
      return co;
128
 
    }
129
 
    
130
 
    private void command_executed (Match match)
131
 
    {
132
 
      CommandObject? co = match as CommandObject;
133
 
      if (co == null) return;
134
 
 
135
 
      past_commands.add (co.command);
136
 
    }
137
 
 
138
 
    public async ResultSet? search (Query q) throws SearchError
139
 
    {
140
 
      // we only search for applications
141
 
      if (!(QueryFlags.APPLICATIONS in q.query_type)) return null;
142
 
 
143
 
      Idle.add (search.callback);
144
 
      yield;
145
 
 
146
 
      var result = new ResultSet ();
147
 
 
148
 
      string stripped = q.query_string.strip ();
149
 
      if (stripped == "") return null;
150
 
      if (stripped.has_prefix ("~/"))
151
 
      {
152
 
        stripped = stripped.replace ("~", Environment.get_home_dir ());
153
 
      }
154
 
 
155
 
      if (!(stripped in past_commands))
156
 
      {
157
 
        foreach (var command in past_commands)
158
 
        {
159
 
          if (command.has_prefix (stripped))
160
 
          {
161
 
            result.add (create_co (command), Match.Score.AVERAGE);
162
 
          }
163
 
        }
164
 
 
165
 
        string[] args = split_regex.split (stripped);
166
 
        string? valid_cmd = Environment.find_program_in_path (args[0]);
167
 
 
168
 
        if (valid_cmd != null)
169
 
        {
170
 
          // don't allow dangerous commands
171
 
          if (args[0] == "rm") return null;
172
 
          CommandObject? co = create_co (stripped);
173
 
          if (co == null) return null;
174
 
          result.add (co, Match.Score.POOR);
175
 
          co.executed.connect (this.command_executed);
176
 
        }
177
 
      }
178
 
      else
179
 
      {
180
 
        result.add (create_co (stripped), Match.Score.VERY_GOOD);
181
 
      }
182
 
      
183
 
      q.check_cancellable ();
184
 
 
185
 
      return result;
186
 
    }
187
 
  }
 
2
* Copyright (c) 2010 Michal Hruby <michal.mhr@gmail.com>
 
3
*               2017 elementary LLC.
 
4
*
 
5
* This program is free software; you can redistribute it and/or
 
6
* modify it under the terms of the GNU General Public
 
7
* License as published by the Free Software Foundation; either
 
8
* version 2 of the License, or (at your option) any later version.
 
9
*
 
10
* This program is distributed in the hope that it will be useful,
 
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
13
* General Public License for more details.
 
14
*
 
15
* You should have received a copy of the GNU General Public
 
16
* License along with this program; if not, write to the
 
17
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 
18
* Boston, MA 02110-1301 USA
 
19
*
 
20
* Authored by: Michal Hruby <michal.mhr@gmail.com>
 
21
*/
 
22
 
 
23
namespace Synapse {
 
24
        public class CommandPlugin: Object, Activatable, ItemProvider {
 
25
                public bool enabled { get; set; default = true; }
 
26
 
 
27
                public void activate () { }
 
28
 
 
29
                public void deactivate () { }
 
30
 
 
31
                private class CommandObject: Object, Match, ApplicationMatch {
 
32
                        // for Match interface
 
33
                        public string title { get; construct set; }
 
34
                        public string description { get; set; default = ""; }
 
35
                        public string icon_name { get; construct set; default = ""; }
 
36
                        public bool has_thumbnail { get; construct set; default = false; }
 
37
                        public string thumbnail_path { get; construct set; }
 
38
                        public MatchType match_type { get; construct set; }
 
39
 
 
40
                        // for ApplicationMatch
 
41
                        public AppInfo? app_info { get; set; default = null; }
 
42
                        public bool needs_terminal { get; set; default = false; }
 
43
                        public string? filename { get; construct set; default = null; }
 
44
                        public string command { get; construct set; }
 
45
 
 
46
                        public CommandObject (string cmd) {
 
47
                                Object (title: _("Execute '%s'").printf (cmd), description: _("Run command"), command: cmd,
 
48
                                                icon_name: "application-x-executable",
 
49
                                                match_type: MatchType.APPLICATION,
 
50
                                                needs_terminal: cmd.has_prefix ("sudo "));
 
51
 
 
52
                                try {
 
53
                                        app_info = AppInfo.create_from_commandline (cmd, null, 0);
 
54
                                } catch (Error err) {
 
55
                                        warning ("%s", err.message);
 
56
                                }
 
57
                        }
 
58
                }
 
59
 
 
60
                static void register_plugin () {
 
61
                        DataSink.PluginRegistry.get_default ().register_plugin (typeof (CommandPlugin),
 
62
                                                                                                                                        "Command Search",
 
63
                                                                                                                                        _("Find and execute arbitrary commands."),
 
64
                                                                                                                                        "system-run",
 
65
                                                                                                                                        register_plugin);
 
66
                }
 
67
 
 
68
                static construct {
 
69
                        register_plugin ();
 
70
                }
 
71
 
 
72
                private Gee.Set<string> past_commands;
 
73
                private Regex split_regex;
 
74
 
 
75
                construct {
 
76
                        // TODO: load from configuration
 
77
                        past_commands = new Gee.HashSet<string> ();
 
78
 
 
79
                        try {
 
80
                                split_regex = new Regex ("\\s+", RegexCompileFlags.OPTIMIZE);
 
81
                        } catch (RegexError err) {
 
82
                                critical ("%s", err.message);
 
83
                        }
 
84
                }
 
85
 
 
86
                private CommandObject? create_co (string exec) {
 
87
                        // ignore results that will be returned by DesktopFilePlugin
 
88
                        // and at the same time look for hidden and no-display desktop files,
 
89
                        // so we can display their info (title, comment, icon)
 
90
                        var dfs = DesktopFileService.get_default ();
 
91
                        var df_list = dfs.get_desktop_files_for_exec (exec);
 
92
                        DesktopFileInfo? dfi = null;
 
93
 
 
94
                        foreach (var df in df_list) {
 
95
                                if (!df.is_hidden) {
 
96
                                        return null; // will be handled by App plugin
 
97
                                }
 
98
                                dfi = df;
 
99
                        }
 
100
 
 
101
                        var co = new CommandObject (exec);
 
102
                        if (dfi != null) {
 
103
                                co.title = dfi.name;
 
104
                                if (dfi.comment != "") {
 
105
                                        co.description = dfi.comment;
 
106
                                }
 
107
 
 
108
                                if (dfi.icon_name != null && dfi.icon_name != "") {
 
109
                                        co.icon_name = dfi.icon_name;
 
110
                                }
 
111
                        }
 
112
 
 
113
                        return co;
 
114
                }
 
115
 
 
116
                private void command_executed (Match match) {
 
117
                        CommandObject? co = match as CommandObject;
 
118
                        if (co == null) {
 
119
                                return;
 
120
                        }
 
121
 
 
122
                        past_commands.add (co.command);
 
123
                }
 
124
 
 
125
                public async ResultSet? search (Query q) throws SearchError {
 
126
                        // we only search for applications
 
127
                        if (!(QueryFlags.APPLICATIONS in q.query_type)) {
 
128
                                return null;
 
129
                        }
 
130
 
 
131
                        Idle.add (search.callback);
 
132
                        yield;
 
133
 
 
134
                        var result = new ResultSet ();
 
135
                        string stripped = q.query_string.strip ();
 
136
 
 
137
                        if (stripped == "") {
 
138
                                return null;
 
139
                        }
 
140
 
 
141
                        if (stripped.has_prefix ("~/")) {
 
142
                                stripped = stripped.replace ("~", Environment.get_home_dir ());
 
143
                        }
 
144
 
 
145
                        if (!(stripped in past_commands)) {
 
146
                                foreach (var command in past_commands) {
 
147
                                        if (command.has_prefix (stripped)) {
 
148
                                                result.add (create_co (command), Match.Score.AVERAGE);
 
149
                                        }
 
150
                                }
 
151
 
 
152
                                string[] args = split_regex.split (stripped);
 
153
                                string? valid_cmd = Environment.find_program_in_path (args[0]);
 
154
 
 
155
                                if (valid_cmd != null) {
 
156
                                        // don't allow dangerous commands
 
157
                                        if (args[0] == "rm") {
 
158
                                                return null;
 
159
                                        }
 
160
                                        CommandObject? co = create_co (stripped);
 
161
                                        if (co == null) {
 
162
                                                return null;
 
163
                                        }
 
164
 
 
165
                                        result.add (co, Match.Score.POOR);
 
166
                                        co.executed.connect (this.command_executed);
 
167
                                }
 
168
                        } else {
 
169
                                result.add (create_co (stripped), Match.Score.VERY_GOOD);
 
170
                        }
 
171
                        q.check_cancellable ();
 
172
 
 
173
                        return result;
 
174
                }
 
175
        }
188
176
}