1 /*
2  * keyboardcast.c - broadcast keystrokes to multiple windows
3  * Copyright © 2005 Ryan Lortie <desrt@desrt.ca>
4  *
5  *   This program is free software; you can redistribute it and/or modify
6  *   it under the terms of version 2 of the GNU General Public License as
7  *   published by the Free Software Foundation.
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 USA
17  */
18 
19 #include <glade/glade.h>
20 #include <gdk/gdkx.h>
21 #include <gtk/gtk.h>
22 #include <stdlib.h>
23 #include <string.h>
24 
25 #include "grab-window.h"
26 #include "window-list.h"
27 
28 static WindowList *wl;
29 GladeXML *xml;
30 
31 static gboolean
window_list_foreach_callback(WindowList * wl,int xid,gpointer user_data)32 window_list_foreach_callback( WindowList *wl, int xid, gpointer user_data )
33 {
34   XKeyEvent *xevent = user_data;
35 
36   xevent->window = xid;
37   gdk_error_trap_push();
38   XSendEvent( xevent->display, xid, FALSE, KeyPressMask, (XEvent *) xevent );
39   gdk_flush();
40   gdk_error_trap_pop();
41 
42   return FALSE;
43 }
44 
45 static gboolean
key_event(GtkWidget * input,GdkEventKey * event)46 key_event( GtkWidget *input, GdkEventKey *event )
47 {
48   XKeyEvent xevent;
49 
50   switch( event->type )
51   {
52     case GDK_KEY_PRESS:
53       xevent.type = KeyPress;
54       break;
55 
56     case GDK_KEY_RELEASE:
57       xevent.type = KeyRelease;
58       break;
59 
60     default:
61       return FALSE;
62   }
63 
64   xevent.display = gdk_x11_get_default_xdisplay();
65   xevent.keycode = event->hardware_keycode;
66   xevent.state = event->state;
67   xevent.time = event->time;
68   xevent.root = gdk_x11_get_default_root_xwindow();
69 
70   window_list_foreach_selected( wl, window_list_foreach_callback, &xevent );
71 
72   return TRUE;
73 }
74 
75 static void
expanded(GtkExpander * expander,GParamSpec * ps,GtkWindow * window)76 expanded( GtkExpander *expander, GParamSpec *ps, GtkWindow *window )
77 {
78   gtk_window_set_resizable( window, gtk_expander_get_expanded( expander ) );
79 }
80 
81 static gboolean
selection_foreach(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)82 selection_foreach( GtkTreeModel *model, GtkTreePath *path,
83                    GtkTreeIter *iter, gpointer data )
84 {
85   const char *criteria = data;
86   const char *name;
87   gboolean active;
88 
89   gtk_tree_model_get( model, iter, 2, &name, -1 );
90 
91   active = criteria && strstr( name, criteria );
92 
93   gtk_list_store_set( GTK_LIST_STORE( model ), iter, 1, active, -1 );
94 
95   return FALSE;
96 }
97 
98 static void
spawn_command(const char * cmd,const char * args)99 spawn_command( const char *cmd, const char *args )
100 {
101   char **argv;
102   int i;
103 
104   argv = g_strsplit( args, ",", 0 );
105 
106   for( i = 0; argv[i]; i++ )
107   {
108     char *command;
109     command = g_strdup_printf( "gnome-terminal --window-with-profile=keyboardcast -t 'Keyboardcast Spawn' -e '%s %s'&", cmd, argv[i] );
110     system( command );
111     g_free( command );
112   }
113 
114   g_strfreev( argv );
115 }
116 
117 static void
update_label(GtkEntry * entry,GtkLabel * label)118 update_label( GtkEntry *entry, GtkLabel *label )
119 {
120   static char *original_label;
121   const char *args;
122   char *new_label;
123   char **argv;
124   int i;
125 
126   if( !original_label )
127     original_label = g_strdup( gtk_label_get_label( label ) );
128 
129   if( entry )
130   {
131     args = gtk_entry_get_text( entry );
132 
133     /* do it this way so it's exactly the same as the other function */
134     argv = g_strsplit( args, ",", 0 );
135     for( i = 0; argv[i]; i++ );
136     g_strfreev( argv );
137   }
138   else
139     i = 4;
140 
141   new_label = g_strdup_printf( original_label, i );
142   gtk_label_set_markup( label, new_label );
143   g_free( new_label );
144 }
145 
146 static void
spawn_clicked(GtkButton * button,GtkDialog * spawn_dialog)147 spawn_clicked( GtkButton *button, GtkDialog *spawn_dialog )
148 {
149   GtkWidget *command, *arguments;
150   const char *cmd, *arg;
151   int response;
152 
153   response = gtk_dialog_run( spawn_dialog );
154   gtk_widget_hide( GTK_WIDGET( spawn_dialog ) );
155 
156   if( response != GTK_RESPONSE_OK )
157     return;
158 
159   command = glade_xml_get_widget( xml, "command-entry" );
160   arguments = glade_xml_get_widget( xml, "arguments-entry" );
161 
162   cmd = gtk_entry_get_text( GTK_ENTRY( command ) );
163   arg = gtk_entry_get_text( GTK_ENTRY( arguments ) );
164 
165   spawn_command( cmd, arg );
166 }
167 
168 static void
button_clicked(GtkButton * button,GtkEntry * select_entry)169 button_clicked( GtkButton *button, GtkEntry *select_entry )
170 {
171   const char *label = gtk_button_get_label( button );
172   const char *criteria;
173 
174   if( !strcmp( label, "_Grab" ) )
175   {
176     window_list_select_xid( wl, grab_window_xid() );
177     return;
178   }
179 
180   if( !strcmp( label, "_Select" ) )
181     criteria = gtk_entry_get_text( select_entry );
182   else if( !strcmp( label, "_None" ) )
183     criteria = NULL;
184   else if( !strcmp( label, "_All" ) )
185     criteria = "";
186   else
187     return;
188 
189   gtk_tree_model_foreach( GTK_TREE_MODEL( wl ),selection_foreach,
190                           (gpointer) criteria );
191 }
192 
193 static void
terminal_toggled(GtkToggleButton * button)194 terminal_toggled( GtkToggleButton *button )
195 {
196   const char *process;
197 
198   if( gtk_toggle_button_get_active( button ) )
199     process = "gnome-terminal";
200   else
201     process = NULL;
202 
203   window_list_filter_by_process( wl, process );
204 }
205 
206 static int
setup_interface(void)207 setup_interface( void )
208 {
209   GtkWidget *window, *treeview, *select_entry, *spawn_dialog, *spawn_label;
210   GtkCellRenderer *renderer;
211 
212   gtk_window_set_default_icon_name( "gnome-dev-keyboard" );
213 
214   wl = window_list_new();
215 
216   xml = glade_xml_new( PREFIX "/share/keyboardcast/keyboardcast.glade",
217                        NULL, NULL );
218 
219   if( xml == NULL )
220     return 1;
221 
222   select_entry = glade_xml_get_widget( xml, "select-entry" );
223   treeview = glade_xml_get_widget( xml, "treeview" );
224   window = glade_xml_get_widget( xml, "window" );
225   spawn_dialog = glade_xml_get_widget( xml, "spawn-dialog" );
226   spawn_label = glade_xml_get_widget( xml, "spawn-label" );
227 
228   if( select_entry == NULL || treeview == NULL ||
229       window == NULL || spawn_dialog == NULL || spawn_label == NULL )
230     return 1;
231 
232   gtk_tree_view_set_model( GTK_TREE_VIEW( treeview ), GTK_TREE_MODEL( wl ) );
233 
234   renderer = window_list_toggle_renderer( wl );
235   gtk_tree_view_insert_column_with_attributes( GTK_TREE_VIEW( treeview ), -1,
236                                                "✓", renderer, // ☑☒✓✔
237                                                "active", 1, NULL );
238 
239   renderer = gtk_cell_renderer_text_new();
240   gtk_tree_view_insert_column_with_attributes( GTK_TREE_VIEW( treeview ), -1,
241                                                "Window Title", renderer,
242                                                "text", 2, NULL );
243 
244   glade_xml_signal_connect( xml, "key_event", G_CALLBACK( key_event ) );
245   glade_xml_signal_connect( xml, "gtk_main_quit", G_CALLBACK( gtk_main_quit ) );
246   glade_xml_signal_connect_data( xml, "expanded",
247                                  G_CALLBACK( expanded ), window );
248   glade_xml_signal_connect_data( xml, "button_clicked",
249                                  G_CALLBACK( button_clicked ), select_entry );
250   glade_xml_signal_connect_data( xml, "spawn_clicked",
251                                  G_CALLBACK( spawn_clicked ), spawn_dialog );
252   glade_xml_signal_connect( xml, "terminal_toggled",
253                             G_CALLBACK( terminal_toggled ) );
254   glade_xml_signal_connect_data( xml, "update_label",
255                                  G_CALLBACK( update_label ), spawn_label );
256 
257   update_label( NULL, GTK_LABEL( spawn_label ) );
258 
259   window_list_filter_by_process( wl, "gnome-terminal" );
260 
261   gtk_widget_show_all( window );
262 
263   return 0;
264 }
265 
266 int
main(int argc,char ** argv)267 main( int argc, char **argv )
268 {
269   int ret;
270 
271   gtk_init( &argc, &argv );
272 
273   if( !(ret = setup_interface()) )
274     gtk_main();
275 
276   return ret;
277 }
278