-rw-r--r-- | noncore/settings/sshkeys/main.cpp | 24 | ||||
-rw-r--r-- | noncore/settings/sshkeys/sshkeys.cpp | 195 | ||||
-rw-r--r-- | noncore/settings/sshkeys/sshkeys.h | 14 | ||||
-rw-r--r-- | noncore/settings/sshkeys/sshkeysbase.ui | 87 |
4 files changed, 278 insertions, 42 deletions
diff --git a/noncore/settings/sshkeys/main.cpp b/noncore/settings/sshkeys/main.cpp index 37be8bf..a7b1d56 100644 --- a/noncore/settings/sshkeys/main.cpp +++ b/noncore/settings/sshkeys/main.cpp | |||
@@ -1,14 +1,30 @@ | |||
1 | 1 | ||
2 | #include <qpe/qpeapplication.h> | 2 | #include <qpe/qpeapplication.h> |
3 | #include "sshkeys.h" | 3 | #include "sshkeys.h" |
4 | #include <stdio.h> | ||
5 | #include <sys/types.h> | ||
6 | #include <sys/stat.h> | ||
7 | #include <fcntl.h> | ||
8 | #include <sys/ioctl.h> | ||
9 | #include <unistd.h> | ||
10 | |||
4 | 11 | ||
5 | int main(int argc, char *argv[]) | 12 | int main(int argc, char *argv[]) |
6 | { | 13 | { |
7 | QPEApplication a(argc, argv); | 14 | QPEApplication a(argc, argv); |
8 | SSHKeysApp app; | 15 | SSHKeysApp app; |
16 | int fd; | ||
17 | |||
18 | /* If we had a controlling TTY, detach from it. | ||
19 | This is to ensure the SSH uses ssh-askpass */ | ||
20 | fd = open("/dev/tty", O_RDONLY); | ||
21 | if (fd != -1) { | ||
22 | ioctl(fd, TIOCNOTTY, NULL); | ||
23 | close(fd); | ||
24 | } | ||
9 | 25 | ||
10 | a.showMainWidget(&app); | 26 | a.showMainWidget(&app); |
11 | 27 | ||
12 | return a.exec(); | 28 | return a.exec(); |
13 | } | 29 | } |
14 | 30 | ||
diff --git a/noncore/settings/sshkeys/sshkeys.cpp b/noncore/settings/sshkeys/sshkeys.cpp index 08ce18d..5095d16 100644 --- a/noncore/settings/sshkeys/sshkeys.cpp +++ b/noncore/settings/sshkeys/sshkeys.cpp | |||
@@ -1,58 +1,227 @@ | |||
1 | #include "sshkeys.h" | 1 | #include "sshkeys.h" |
2 | 2 | ||
3 | #include <qpe/qpeapplication.h> | 3 | #include <qpe/qpeapplication.h> |
4 | #include <opie/oprocess.h> | 4 | #include <opie/oprocess.h> |
5 | #include <qmultilineedit.h> | 5 | #include <qmultilineedit.h> |
6 | #include <qpushbutton.h> | 6 | #include <qpushbutton.h> |
7 | #include <qtable.h> | 7 | #include <qlistview.h> |
8 | #include <qcombobox.h> | ||
9 | |||
10 | #include <sys/types.h> | ||
11 | #include <sys/stat.h> | ||
12 | #include <stdlib.h> | ||
13 | #include <unistd.h> | ||
14 | #include <stdio.h> | ||
15 | #include <ctype.h> | ||
16 | |||
17 | static char *keynames[] = { "identity", "id_rsa", "id_dsa" }; | ||
8 | 18 | ||
9 | SSHKeysApp::SSHKeysApp( QWidget* parent, const char* name, WFlags fl ) | 19 | SSHKeysApp::SSHKeysApp( QWidget* parent, const char* name, WFlags fl ) |
10 | : SSHKeysBase( parent, name, fl ) | 20 | : SSHKeysBase( parent, name, fl ) |
11 | { | 21 | { |
22 | char *home = getenv("HOME"); | ||
23 | unsigned i; | ||
12 | 24 | ||
13 | connect(AddButton, SIGNAL(clicked()), this, SLOT(doAddButton())); | 25 | connect(AddButton, SIGNAL(clicked()), this, SLOT(doAddButton())); |
14 | connect(RefreshListButton, SIGNAL(clicked()), this, SLOT(doRefreshListButton())); | 26 | connect(RefreshListButton, SIGNAL(clicked()), this, SLOT(doRefreshListButton())); |
15 | connect(RemoveAllButton, SIGNAL(clicked()), this, SLOT(doRemoveAllButton())); | 27 | connect(RemoveAllButton, SIGNAL(clicked()), this, SLOT(doRemoveAllButton())); |
16 | 28 | ||
17 | KeyList->horizontalHeader()->setLabel(0, tr("Key")); | 29 | connect(&addprocess, SIGNAL(receivedStdout(OProcess*,char*,int)), |
18 | KeyList->horizontalHeader()->setLabel(1, tr("Size")); | 30 | this, SLOT(log_sshadd_output(OProcess*,char*,int))); |
19 | KeyList->horizontalHeader()->setLabel(2, tr("Fingerprint")); | 31 | connect(&addprocess, SIGNAL(processExited(OProcess*)), |
32 | this, SLOT(ssh_add_exited(OProcess*))); | ||
33 | |||
34 | connect(KeyFileName, SIGNAL(textChanged(const QString&)), | ||
35 | this, SLOT(add_text_changed(const QString&))); | ||
36 | |||
37 | if (home) { | ||
38 | for (i = 0; i < sizeof(keynames)/sizeof(keynames[0]); i++) { | ||
39 | char thiskeyname[32]; | ||
40 | |||
41 | thiskeyname[31] = 0; | ||
42 | snprintf(thiskeyname, 31, "%s/.ssh/%s", home, keynames[i]); | ||
43 | if (!access(thiskeyname, R_OK)) { | ||
44 | KeyFileName->insertItem(thiskeyname); | ||
45 | } | ||
46 | } | ||
47 | } | ||
20 | 48 | ||
21 | doRefreshListButton(); | 49 | doRefreshListButton(); |
22 | } | 50 | } |
23 | 51 | ||
24 | SSHKeysApp::~SSHKeysApp() | 52 | SSHKeysApp::~SSHKeysApp() |
25 | { | 53 | { |
26 | } | 54 | } |
27 | 55 | ||
28 | void SSHKeysApp::doRefreshListButton() | 56 | void SSHKeysApp::doRefreshListButton() |
29 | { | 57 | { |
30 | OProcess sshadd_process; | 58 | OProcess sshadd_process; |
59 | QListViewItem *t = KeyList->firstChild(); | ||
31 | 60 | ||
61 | while(t) { | ||
62 | QListViewItem *next = t->nextSibling(); | ||
63 | KeyList->takeItem(t); | ||
64 | delete(t); | ||
65 | t = next; | ||
66 | } | ||
67 | |||
32 | connect(&sshadd_process, SIGNAL(receivedStdout(OProcess*,char*,int)), | 68 | connect(&sshadd_process, SIGNAL(receivedStdout(OProcess*,char*,int)), |
33 | this, SLOT(get_list_keys_output(OProcess*,char*,int))); | 69 | this, SLOT(get_list_keys_output(OProcess*,char*,int))); |
34 | 70 | ||
35 | TextOutput->append("Running ssh-add -l\n"); | 71 | keystate = KeySize; |
72 | incoming_keyname=""; | ||
73 | incoming_keysize=""; | ||
74 | incoming_keyfingerprint=""; | ||
75 | |||
76 | //log_text("Running ssh-add -l"); | ||
36 | sshadd_process << "ssh-add" << "-l"; | 77 | sshadd_process << "ssh-add" << "-l"; |
37 | bool ret = sshadd_process.start(OProcess::Block, OProcess::AllOutput); | 78 | bool ret = sshadd_process.start(OProcess::Block, OProcess::AllOutput); |
38 | if (!ret) | 79 | if (!ret) { |
39 | TextOutput->append("Error running ssh-add\n"); | 80 | log_text("Error running ssh-add"); |
40 | KeyList->setText(0, 0, "dwmw2@infradead.org (RSA v1)"); | 81 | return; |
41 | KeyList->setText(0, 1, "1024"); | 82 | } |
42 | KeyList->setText(0, 2, "78:24:04:95:40:fc:b2:80:9b:94:d5:ae:19:56:19:65"); | 83 | |
43 | } | 84 | } |
44 | 85 | ||
45 | void SSHKeysApp::get_list_keys_output(OProcess *proc, char *buffer, int buflen) | 86 | void SSHKeysApp::get_list_keys_output(OProcess *proc, char *buffer, int buflen) |
46 | { | 87 | { |
47 | TextOutput->append(buffer); | 88 | int i; |
89 | (void) proc; | ||
90 | |||
91 | for (i=0; i<buflen; i++) { | ||
92 | switch(keystate) { | ||
93 | case Noise: | ||
94 | noise: | ||
95 | if (buffer[i] == '\n') { | ||
96 | log_text(incoming_noise.local8Bit()); | ||
97 | incoming_noise = ""; | ||
98 | keystate = KeySize; | ||
99 | } else { | ||
100 | incoming_noise += buffer[i]; | ||
101 | } | ||
102 | break; | ||
103 | |||
104 | case KeySize: | ||
105 | if (isdigit(buffer[i])) { | ||
106 | incoming_keysize += buffer[i]; | ||
107 | } else if (buffer[i] == ' ') { | ||
108 | keystate = KeyFingerprint; | ||
109 | } else { | ||
110 | incoming_keysize = ""; | ||
111 | incoming_noise = ""; | ||
112 | keystate = Noise; | ||
113 | goto noise; | ||
114 | } | ||
115 | break; | ||
116 | |||
117 | case KeyFingerprint: | ||
118 | if (isxdigit(buffer[i]) || buffer[i] == ':') { | ||
119 | incoming_keyfingerprint += buffer[i]; | ||
120 | } else if (buffer[i] == ' ') { | ||
121 | keystate = KeyName; | ||
122 | } else { | ||
123 | incoming_keysize = ""; | ||
124 | incoming_keyfingerprint = ""; | ||
125 | incoming_noise = ""; | ||
126 | keystate = Noise; | ||
127 | goto noise; | ||
128 | } | ||
129 | break; | ||
130 | |||
131 | case KeyName: | ||
132 | if (buffer[i] == '\n') { | ||
133 | /* Wheee. Got one. */ | ||
134 | KeyList->insertItem(new | ||
135 | QListViewItem(KeyList, incoming_keyname, incoming_keysize, incoming_keyfingerprint)); | ||
136 | incoming_keysize = ""; | ||
137 | incoming_keyfingerprint = ""; | ||
138 | incoming_keyname = ""; | ||
139 | keystate = KeySize; | ||
140 | } else if (isprint(buffer[i])) { | ||
141 | incoming_keyname += buffer[i]; | ||
142 | } else { | ||
143 | incoming_keysize = ""; | ||
144 | incoming_keyfingerprint = ""; | ||
145 | incoming_noise = ""; | ||
146 | keystate = Noise; | ||
147 | goto noise; | ||
148 | } | ||
149 | break; | ||
150 | } | ||
151 | } | ||
48 | } | 152 | } |
49 | 153 | ||
154 | void SSHKeysApp::log_sshadd_output(OProcess *proc, char *buffer, int buflen) | ||
155 | { | ||
156 | (void)proc; | ||
157 | (void)buflen; | ||
158 | |||
159 | log_text(buffer); | ||
160 | } | ||
161 | |||
162 | void SSHKeysApp::ssh_add_exited(OProcess *proc) | ||
163 | { | ||
164 | (void)proc; | ||
165 | |||
166 | doRefreshListButton(); | ||
167 | setEnabled(TRUE); | ||
168 | if (proc->exitStatus()) { | ||
169 | |||
170 | log_text(QString("ssh-add failed")); | ||
171 | } | ||
172 | } | ||
173 | |||
174 | void SSHKeysApp::add_text_changed(const QString &text) | ||
175 | { | ||
176 | struct stat sbuf; | ||
177 | |||
178 | if (!text.length() || (!access(text.ascii(), R_OK) && | ||
179 | !stat(text.ascii(), &sbuf) && | ||
180 | S_ISREG(sbuf.st_mode))) | ||
181 | AddButton->setEnabled(TRUE); | ||
182 | else | ||
183 | AddButton->setEnabled(FALSE); | ||
184 | } | ||
50 | 185 | ||
51 | void SSHKeysApp::doAddButton() | 186 | void SSHKeysApp::doAddButton() |
52 | { | 187 | { |
188 | addprocess.clearArguments(); | ||
189 | |||
190 | setEnabled(FALSE); | ||
191 | |||
192 | if (KeyFileName->currentText().length()) { | ||
193 | addprocess << "ssh-add" << "--" << KeyFileName->currentText(); | ||
194 | log_text(QString("Running ssh-add -- ") + KeyFileName->currentText()); | ||
195 | } else { | ||
196 | addprocess << "ssh-add"; | ||
197 | log_text("Running ssh-add"); | ||
198 | } | ||
199 | bool ret = addprocess.start(OProcess::NotifyOnExit, OProcess::AllOutput); | ||
200 | if (!ret) { | ||
201 | log_text("Error running ssh-add"); | ||
202 | doRefreshListButton(); | ||
203 | setEnabled(TRUE); | ||
204 | } | ||
205 | } | ||
53 | 206 | ||
207 | void SSHKeysApp::log_text(const char *text) | ||
208 | { | ||
209 | TextOutput->append(text); | ||
210 | TextOutput->setCursorPosition(TextOutput->numLines()+1, 0, FALSE); | ||
54 | } | 211 | } |
212 | |||
55 | void SSHKeysApp::doRemoveAllButton() | 213 | void SSHKeysApp::doRemoveAllButton() |
56 | { | 214 | { |
57 | 215 | OProcess sshadd_process; | |
216 | |||
217 | connect(&sshadd_process, SIGNAL(receivedStdout(OProcess*,char*,int)), | ||
218 | this, SLOT(log_sshadd_output(OProcess*,char*,int))); | ||
219 | |||
220 | log_text("Running ssh-add -D"); | ||
221 | sshadd_process << "ssh-add" << "-D"; | ||
222 | bool ret = sshadd_process.start(OProcess::Block, OProcess::AllOutput); | ||
223 | if (!ret) { | ||
224 | log_text("Error running ssh-add"); | ||
225 | } | ||
226 | doRefreshListButton(); | ||
58 | } | 227 | } |
diff --git a/noncore/settings/sshkeys/sshkeys.h b/noncore/settings/sshkeys/sshkeys.h index 4a9f2fe..9a39a2c 100644 --- a/noncore/settings/sshkeys/sshkeys.h +++ b/noncore/settings/sshkeys/sshkeys.h | |||
@@ -1,27 +1,35 @@ | |||
1 | 1 | ||
2 | #ifndef SSHKEYSAPP_H | 2 | #ifndef SSHKEYSAPP_H |
3 | #define SSHKEYSAPP_H | 3 | #define SSHKEYSAPP_H |
4 | 4 | ||
5 | #include "sshkeysbase.h" | 5 | #include "sshkeysbase.h" |
6 | 6 | #include <opie/oprocess.h> | |
7 | class OProcess; | ||
8 | 7 | ||
9 | class SSHKeysApp : public SSHKeysBase | 8 | class SSHKeysApp : public SSHKeysBase |
10 | { | 9 | { |
11 | Q_OBJECT | 10 | Q_OBJECT |
12 | 11 | ||
13 | public: | 12 | public: |
14 | SSHKeysApp( QWidget* parent = 0, const char* name = 0, WFlags fl = 0 ); | 13 | SSHKeysApp( QWidget* parent = 0, const char* name = 0, WFlags fl = 0 ); |
15 | ~SSHKeysApp(); | 14 | ~SSHKeysApp(); |
16 | 15 | ||
17 | private: | 16 | private: |
18 | void sshadd(char **args); | 17 | void log_text(const char *text); |
18 | enum { Noise, KeyName, KeySize, KeyFingerprint } keystate; | ||
19 | QString incoming_keyname; | ||
20 | QString incoming_keysize; | ||
21 | QString incoming_keyfingerprint; | ||
22 | QString incoming_noise; | ||
23 | OProcess addprocess; | ||
19 | 24 | ||
20 | private slots: | 25 | private slots: |
21 | void doAddButton(); | 26 | void doAddButton(); |
22 | void doRefreshListButton(); | 27 | void doRefreshListButton(); |
23 | void doRemoveAllButton(); | 28 | void doRemoveAllButton(); |
24 | void get_list_keys_output(OProcess *proc, char *buffer, int buflen); | 29 | void get_list_keys_output(OProcess *proc, char *buffer, int buflen); |
30 | void log_sshadd_output(OProcess *proc, char *buffer, int buflen); | ||
31 | void ssh_add_exited(OProcess *proc); | ||
32 | void add_text_changed(const QString &text); | ||
25 | }; | 33 | }; |
26 | #endif | 34 | #endif |
27 | 35 | ||
diff --git a/noncore/settings/sshkeys/sshkeysbase.ui b/noncore/settings/sshkeys/sshkeysbase.ui index dc1df28..4c9daa9 100644 --- a/noncore/settings/sshkeys/sshkeysbase.ui +++ b/noncore/settings/sshkeys/sshkeysbase.ui | |||
@@ -19,26 +19,32 @@ | |||
19 | <name>caption</name> | 19 | <name>caption</name> |
20 | <string>SSH Keys</string> | 20 | <string>SSH Keys</string> |
21 | </property> | 21 | </property> |
22 | <grid> | 22 | <grid> |
23 | <property stdset="1"> | 23 | <property stdset="1"> |
24 | <name>margin</name> | 24 | <name>margin</name> |
25 | <number>11</number> | 25 | <number>11</number> |
26 | </property> | 26 | </property> |
27 | <property stdset="1"> | 27 | <property stdset="1"> |
28 | <name>spacing</name> | 28 | <name>spacing</name> |
29 | <number>6</number> | 29 | <number>6</number> |
30 | </property> | 30 | </property> |
31 | <widget row="0" column="1" rowspan="1" colspan="2" > | 31 | <widget row="0" column="1" > |
32 | <class>QComboBox</class> | 32 | <class>QComboBox</class> |
33 | <item> | ||
34 | <property> | ||
35 | <name>text</name> | ||
36 | <string></string> | ||
37 | </property> | ||
38 | </item> | ||
33 | <property stdset="1"> | 39 | <property stdset="1"> |
34 | <name>name</name> | 40 | <name>name</name> |
35 | <cstring>KeyFileName</cstring> | 41 | <cstring>KeyFileName</cstring> |
36 | </property> | 42 | </property> |
37 | <property stdset="1"> | 43 | <property stdset="1"> |
38 | <name>sizePolicy</name> | 44 | <name>sizePolicy</name> |
39 | <sizepolicy> | 45 | <sizepolicy> |
40 | <hsizetype>7</hsizetype> | 46 | <hsizetype>7</hsizetype> |
41 | <vsizetype>0</vsizetype> | 47 | <vsizetype>0</vsizetype> |
42 | </sizepolicy> | 48 | </sizepolicy> |
43 | </property> | 49 | </property> |
44 | <property stdset="1"> | 50 | <property stdset="1"> |
@@ -74,70 +80,107 @@ | |||
74 | <name>text</name> | 80 | <name>text</name> |
75 | <string>Refresh</string> | 81 | <string>Refresh</string> |
76 | </property> | 82 | </property> |
77 | </widget> | 83 | </widget> |
78 | <widget row="1" column="1" > | 84 | <widget row="1" column="1" > |
79 | <class>QPushButton</class> | 85 | <class>QPushButton</class> |
80 | <property stdset="1"> | 86 | <property stdset="1"> |
81 | <name>name</name> | 87 | <name>name</name> |
82 | <cstring>RemoveAllButton</cstring> | 88 | <cstring>RemoveAllButton</cstring> |
83 | </property> | 89 | </property> |
84 | <property stdset="1"> | 90 | <property stdset="1"> |
85 | <name>enabled</name> | 91 | <name>enabled</name> |
86 | <bool>false</bool> | 92 | <bool>true</bool> |
87 | </property> | 93 | </property> |
88 | <property stdset="1"> | 94 | <property stdset="1"> |
89 | <name>text</name> | 95 | <name>text</name> |
90 | <string>Clear Keys</string> | 96 | <string>Clear Keys</string> |
91 | </property> | 97 | </property> |
92 | </widget> | 98 | </widget> |
93 | <widget row="3" column="0" rowspan="1" colspan="3" > | 99 | <widget row="3" column="0" rowspan="1" colspan="2" > |
94 | <class>QMultiLineEdit</class> | 100 | <class>QMultiLineEdit</class> |
95 | <property stdset="1"> | 101 | <property stdset="1"> |
96 | <name>name</name> | 102 | <name>name</name> |
97 | <cstring>TextOutput</cstring> | 103 | <cstring>TextOutput</cstring> |
98 | </property> | 104 | </property> |
99 | <property stdset="1"> | 105 | <property stdset="1"> |
106 | <name>enabled</name> | ||
107 | <bool>true</bool> | ||
108 | </property> | ||
109 | <property stdset="1"> | ||
100 | <name>sizePolicy</name> | 110 | <name>sizePolicy</name> |
101 | <sizepolicy> | 111 | <sizepolicy> |
102 | <hsizetype>7</hsizetype> | 112 | <hsizetype>7</hsizetype> |
103 | <vsizetype>1</vsizetype> | 113 | <vsizetype>1</vsizetype> |
104 | </sizepolicy> | 114 | </sizepolicy> |
105 | </property> | 115 | </property> |
106 | <property stdset="1"> | 116 | <property stdset="1"> |
117 | <name>font</name> | ||
118 | <font> | ||
119 | <family>adobe-helvetica</family> | ||
120 | <pointsize>8</pointsize> | ||
121 | </font> | ||
122 | </property> | ||
123 | <property stdset="1"> | ||
107 | <name>readOnly</name> | 124 | <name>readOnly</name> |
108 | <bool>true</bool> | 125 | <bool>true</bool> |
109 | </property> | 126 | </property> |
110 | </widget> | 127 | </widget> |
111 | <widget row="2" column="0" rowspan="1" colspan="3" > | 128 | <widget row="2" column="0" rowspan="1" colspan="2" > |
112 | <class>QTable</class> | 129 | <class>QListView</class> |
130 | <column> | ||
131 | <property> | ||
132 | <name>text</name> | ||
133 | <string>Key Name</string> | ||
134 | </property> | ||
135 | <property> | ||
136 | <name>clickable</name> | ||
137 | <bool>true</bool> | ||
138 | </property> | ||
139 | <property> | ||
140 | <name>resizeable</name> | ||
141 | <bool>true</bool> | ||
142 | </property> | ||
143 | </column> | ||
144 | <column> | ||
145 | <property> | ||
146 | <name>text</name> | ||
147 | <string>Size</string> | ||
148 | </property> | ||
149 | <property> | ||
150 | <name>clickable</name> | ||
151 | <bool>true</bool> | ||
152 | </property> | ||
153 | <property> | ||
154 | <name>resizeable</name> | ||
155 | <bool>true</bool> | ||
156 | </property> | ||
157 | </column> | ||
158 | <column> | ||
159 | <property> | ||
160 | <name>text</name> | ||
161 | <string>Fingerprint</string> | ||
162 | </property> | ||
163 | <property> | ||
164 | <name>clickable</name> | ||
165 | <bool>true</bool> | ||
166 | </property> | ||
167 | <property> | ||
168 | <name>resizeable</name> | ||
169 | <bool>true</bool> | ||
170 | </property> | ||
171 | </column> | ||
113 | <property stdset="1"> | 172 | <property stdset="1"> |
114 | <name>name</name> | 173 | <name>name</name> |
115 | <cstring>KeyList</cstring> | 174 | <cstring>KeyList</cstring> |
116 | </property> | 175 | </property> |
117 | <property stdset="1"> | 176 | <property stdset="1"> |
118 | <name>font</name> | 177 | <name>font</name> |
119 | <font> | 178 | <font> |
120 | <family>adobe-helvetica</family> | 179 | <family>adobe-helvetica</family> |
121 | <pointsize>9</pointsize> | 180 | <pointsize>8</pointsize> |
122 | </font> | 181 | </font> |
123 | </property> | 182 | </property> |
124 | <property stdset="1"> | ||
125 | <name>resizePolicy</name> | ||
126 | <enum>AutoOneFit</enum> | ||
127 | </property> | ||
128 | <property stdset="1"> | ||
129 | <name>numRows</name> | ||
130 | <number>1</number> | ||
131 | </property> | ||
132 | <property stdset="1"> | ||
133 | <name>numCols</name> | ||
134 | <number>3</number> | ||
135 | </property> | ||
136 | <property stdset="1"> | ||
137 | <name>showGrid</name> | ||
138 | <bool>false</bool> | ||
139 | </property> | ||
140 | </widget> | 183 | </widget> |
141 | </grid> | 184 | </grid> |
142 | </widget> | 185 | </widget> |
143 | </UI> | 186 | </UI> |