├── .gitignore ├── doc ├── callgraph.pdf ├── internals.dot ├── Makefile └── tags_to_dot.rb ├── src ├── ts_verbose.h ├── ts_defines.h ├── xorg │ ├── ts_xorg.h │ ├── ts_xorg_keymap.c │ └── ts_xorg_client.c ├── ts_clipboard.h ├── ts_signal.h ├── ts_clipboard.c ├── ts_display_proxy.h ├── ts_master.h ├── ts_signal.c ├── ts_mux.h ├── ts_master.c ├── ts_display.c ├── fifo_declare.h ├── osx │ ├── ts_osx_keymap.c │ ├── ts_osx_clipboard.c │ └── ts_osx_server.c ├── ts_display.h ├── ts_keymap.h ├── ts_display_proxy.c └── ts_mux.c ├── Makefile.common ├── Makefile ├── README.md └── cmd └── touchstream.c /.gitignore: -------------------------------------------------------------------------------- 1 | synergy-1.4.2-Source 2 | obj-* 3 | .*project 4 | .DS_Store 5 | ._* 6 | .tags* 7 | -------------------------------------------------------------------------------- /doc/callgraph.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/touchstream/HEAD/doc/callgraph.pdf -------------------------------------------------------------------------------- /doc/internals.dot: -------------------------------------------------------------------------------- 1 | digraph dump { 2 | node [shape=rect]; compound=true; nodesep=.1; ranksep=2; rankdir=LR; concentrate=true 3 | ; 4 | 5 | subgraph clusters_osx_server { 6 | ts_osx_server; 7 | ts_osx_keymap; 8 | ts_osx_clipboard; 9 | } 10 | subgraph clusters_xorg_client { 11 | ts_xorg_client; 12 | ts_xorg_keymap; 13 | } 14 | subgraph clusters_display { 15 | ts_display; 16 | ts_clipboard; 17 | } 18 | ts_master -> ts_display; 19 | ts_display -> ts_xorg_client; 20 | ts_display -> ts_display_proxy; 21 | ts_display -> ts_mux_driver_remote; 22 | ts_display -> ts_osx_server; 23 | 24 | ts_mux -> ts_remote; 25 | 26 | ts_remote -> ts_remote_listen ; 27 | ts_remote -> ts_remote_data ; 28 | ts_remote -> ts_remote_connect ; 29 | ts_remote_connect -> ts_remote_data; 30 | ts_remote_listen -> ts_remote_data; 31 | 32 | ts_remote -> ts_xorg_client; 33 | ts_remote -> ts_display_proxy; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/ts_verbose.h: -------------------------------------------------------------------------------- 1 | /* 2 | ts_verbose.h 3 | 4 | Copyright 2011 Michel Pollet 5 | 6 | This file is part of simavr. 7 | 8 | simavr is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | simavr is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with simavr. If not, see . 20 | */ 21 | 22 | 23 | #ifndef __TS_VERBOSE_H___ 24 | #define __TS_VERBOSE_H___ 25 | 26 | void V1(const char * format, ...); 27 | void V2(const char * format, ...); 28 | void V3(const char * format, ...); 29 | 30 | #endif /* __TS_VERBOSE_H___ */ 31 | -------------------------------------------------------------------------------- /src/ts_defines.h: -------------------------------------------------------------------------------- 1 | /* 2 | ts_defines.h 3 | 4 | Copyright 2011 Michel Pollet 5 | 6 | This file is part of touchstream. 7 | 8 | touchstream is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | touchstream is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with touchstream. If not, see . 20 | */ 21 | 22 | 23 | #ifndef __TS_DEFINES_H___ 24 | #define __TS_DEFINES_H___ 25 | 26 | #include "ts_display.h" 27 | #include "ts_master.h" 28 | 29 | struct ts_mux_t; 30 | typedef struct ts_display_t * 31 | (*ts_platform_create_callback_p)( 32 | struct ts_mux_t * mux, 33 | struct ts_master_t * master, 34 | char * param); 35 | 36 | #endif /* __TS_DEFINES_H___ */ 37 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2008, 2009 Michel Pollet 3 | # 4 | # This file is part of touchstream. 5 | # 6 | # touchstream is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # touchstream is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with touchstream. If not, see . 18 | 19 | # you need Graphviz, ruby and exuberant ctags here. 20 | # this is not generated in the normal code build 21 | 22 | all: callgraph.pdf 23 | 24 | callgraph.pdf: 25 | ctags -f .tags ../src/*.[ch] ../src/*/*.[ch] 2>/dev/null && \ 26 | ruby ./tags_to_dot.rb .tags \ 27 | ../src/*.c ../src/*/*.c >.tags.dot && \ 28 | dot -Tpdf .tags.dot -o $@ 29 | 30 | clean: 31 | rm -f .tags* 32 | -------------------------------------------------------------------------------- /src/xorg/ts_xorg.h: -------------------------------------------------------------------------------- 1 | /* 2 | ts_xorg.h 3 | 4 | Copyright 2011 Michel Pollet 5 | 6 | This file is part of touchstream. 7 | 8 | touchstream is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | touchstream is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with touchstream. If not, see . 20 | */ 21 | 22 | 23 | #ifndef __TS_XORG_H___ 24 | #define __TS_XORG_H___ 25 | 26 | typedef struct ts_xorg_krev_t { 27 | struct { 28 | int count; 29 | struct { 30 | unsigned long key : 16, code : 8; 31 | } v[4]; 32 | } map[255]; 33 | } ts_xorg_krev_t, * ts_xorg_krev_p; 34 | 35 | int 36 | ts_xorg_keymap_load( 37 | ts_xorg_krev_p map, 38 | Display * m_display); 39 | 40 | int 41 | ts_xorg_key_to_button( 42 | ts_xorg_krev_p map, 43 | uint32_t key); 44 | 45 | #endif /* __TS_XORG_H___ */ 46 | -------------------------------------------------------------------------------- /src/ts_clipboard.h: -------------------------------------------------------------------------------- 1 | /* 2 | ts_clipboard.h 3 | 4 | Copyright 2011 Michel Pollet 5 | 6 | This file is part of touchstream. 7 | 8 | touchstream is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | touchstream is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with touchstream. If not, see . 20 | */ 21 | 22 | /* 23 | * This small structure is made to store various "flavors" of clipboard 24 | * data. Right now only strings are supported, but pretty much anything 25 | * can be stored in there... 26 | */ 27 | #ifndef __TS_CLIPBOARD_H___ 28 | #define __TS_CLIPBOARD_H___ 29 | 30 | #include 31 | #include 32 | 33 | typedef struct ts_clipboard_t { 34 | int flavorCount; 35 | struct { 36 | char * name; 37 | size_t size; 38 | uint8_t * data; 39 | } flavor[8]; 40 | } ts_clipboard_t, *ts_clipboard_p; 41 | 42 | void 43 | ts_clipboard_clear( 44 | ts_clipboard_p clip ); 45 | 46 | int 47 | ts_clipboard_add( 48 | ts_clipboard_p clip, 49 | char * flavor, 50 | uint8_t * data, 51 | size_t size ); 52 | 53 | 54 | #endif /* __TS_CLIPBOARD_H___ */ 55 | -------------------------------------------------------------------------------- /src/ts_signal.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ts_signal.h 3 | * 4 | Copyright 2011 Michel Pollet 5 | 6 | This file is part of touchstream. 7 | 8 | touchstream is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | touchstream is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with touchstream. If not, see . 20 | */ 21 | 22 | /* 23 | * Small utility that uses a socketpair as a semaphore that can be 24 | * used with select() 25 | */ 26 | #ifndef TS_SIGNAL_H_ 27 | #define TS_SIGNAL_H_ 28 | 29 | #include 30 | 31 | enum { 32 | TS_SIGNAL_END0 = 0, 33 | TS_SIGNAL_END1 34 | }; 35 | 36 | typedef struct ts_signal_t { 37 | int fd[2]; 38 | } ts_signal_t, *ts_signal_p; 39 | 40 | int 41 | ts_signal_init( 42 | ts_signal_p s); 43 | 44 | void 45 | ts_signal_dispose( 46 | ts_signal_p s); 47 | 48 | //! Signal the one side 49 | void 50 | ts_signal( 51 | ts_signal_p s, 52 | int end, 53 | uint8_t what ); 54 | 55 | void 56 | ts_signal_flush( 57 | ts_signal_p s, 58 | int end ); 59 | 60 | //! Signal wait 61 | uint8_t 62 | ts_signal_wait( 63 | ts_signal_p s, 64 | int end, 65 | uint32_t inTimeout /* in milliseconds */ ); 66 | 67 | #endif /* VD_SIGNAL_H_ */ 68 | -------------------------------------------------------------------------------- /Makefile.common: -------------------------------------------------------------------------------- 1 | # Makefile.common 2 | # 3 | # Copyright 2011 Michel Pollet 4 | # 5 | # This file is part of touchstream. 6 | # 7 | # touchstream is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # touchstream is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with touchstream. If not, see . 19 | 20 | DESTDIR ?= /usr/local 21 | CFLAGS ?= -O2 -g -Wall #-Werror 22 | 23 | OBJ := obj-$(shell $(CC) -dumpmachine) 24 | 25 | EXTRA_CFLAGS += --std=gnu99 26 | EXTRA_CFLAGS += -ffunction-sections -fdata-sections 27 | EXTRA_CFLAGS += ${patsubst %,-I%,${subst :, ,${IPATH}}} 28 | 29 | # 30 | # Generic rules 31 | # 32 | $(OBJ): 33 | @mkdir -p $(OBJ) 34 | 35 | $(OBJ)/%.a : 36 | ifeq ($(V),1) 37 | $(AR) cru $@ $^ 38 | ranlib $@ 39 | else 40 | @echo " LIB ${@}" 41 | @$(AR) cru $@ $^ \ 42 | || echo Error: $(AR) cru $@ $^ 43 | endif 44 | 45 | ${OBJ}/%.o: %.c 46 | ifeq ($(V),1) 47 | ${CC} -MD ${EXTRA_CFLAGS} ${CPPFLAGS} ${CFLAGS} ${CFLAGS_TARGET} -c -o ${@} ${<} 48 | else 49 | @echo " CC ${<}" 50 | @${CC} -MD ${EXTRA_CFLAGS} ${CPPFLAGS} ${CFLAGS} ${CFLAGS_TARGET} -c -o ${@} ${<} \ 51 | || echo Error: ${CC} -MD ${EXTRA_CFLAGS} ${CPPFLAGS} ${CFLAGS} ${CFLAGS_TARGET} -c -o ${@} ${<} 52 | endif 53 | 54 | ${OBJ}/%.bin: 55 | ifeq ($(V),1) 56 | ${CC} -o ${@} ${^} ${LDFLAGS_TARGET} ${EXTRA_LDFLAGS} ${LDFLAGS} 57 | else 58 | @echo " LD ${*}" 59 | @${CC} -o ${@} ${^} ${LDFLAGS_TARGET} ${EXTRA_LDFLAGS} ${LDFLAGS} \ 60 | || echo Error: ${CC} -o ${@} ${^} ${LDFLAGS_TARGET} ${EXTRA_LDFLAGS} ${LDFLAGS} 61 | endif 62 | 63 | clean: 64 | rm -rf obj-* 65 | 66 | # Include autogenerated dependencies 67 | -include ${OBJ}/*.d 68 | -------------------------------------------------------------------------------- /src/ts_clipboard.c: -------------------------------------------------------------------------------- 1 | /* 2 | ts_clipboard.c 3 | 4 | Copyright 2011 Michel Pollet 5 | 6 | This file is part of touchstream. 7 | 8 | touchstream is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | touchstream is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with touchstream. If not, see . 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include "ts_clipboard.h" 26 | 27 | void 28 | ts_clipboard_clear( 29 | ts_clipboard_p clip ) 30 | { 31 | for (int i = 0; i < clip->flavorCount; i++) { 32 | if (clip->flavor[i].name) 33 | free(clip->flavor[i].name); 34 | if (clip->flavor[i].data) 35 | free(clip->flavor[i].data); 36 | clip->flavor[i].name = NULL; 37 | clip->flavor[i].data = NULL; 38 | clip->flavor[i].size = 0; 39 | } 40 | clip->flavorCount = 0; 41 | } 42 | 43 | int 44 | ts_clipboard_add( 45 | ts_clipboard_p clip, 46 | char * flavor, 47 | uint8_t * data, 48 | size_t size ) 49 | { 50 | int slot = -1; 51 | for (int i = 0; i < clip->flavorCount; i++) 52 | if (!strcmp(clip->flavor[i].name, flavor)) { 53 | slot = i; 54 | break; 55 | } 56 | if (slot == -1) { 57 | if (clip->flavorCount == (sizeof(clip->flavor) / sizeof(clip->flavor[0]))) 58 | return -1; 59 | slot = clip->flavorCount++; 60 | clip->flavor[slot].name = strdup(flavor); 61 | } 62 | clip->flavor[slot].data = realloc( 63 | clip->flavor[slot].data, 64 | clip->flavor[slot].size + size + 1); 65 | memcpy(clip->flavor[slot].data + clip->flavor[slot].size, 66 | data, size); 67 | clip->flavor[slot].size += size; 68 | clip->flavor[slot].data[clip->flavor[slot].size] = 0; 69 | return 0; 70 | } 71 | -------------------------------------------------------------------------------- /src/ts_display_proxy.h: -------------------------------------------------------------------------------- 1 | /* 2 | ts_display_proxy.h 3 | 4 | Copyright 2011 Michel Pollet 5 | 6 | This file is part of touchstream. 7 | 8 | touchstream is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | touchstream is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with touchstream. If not, see . 20 | */ 21 | 22 | 23 | #ifndef __TS_DISPLAY_PROXY_H___ 24 | #define __TS_DISPLAY_PROXY_H___ 25 | 26 | #include "ts_display.h" 27 | #include "ts_signal.h" 28 | #include "ts_mux.h" 29 | 30 | enum { 31 | ts_proxy_init = 0, 32 | ts_proxy_dispose, 33 | ts_proxy_enter, 34 | ts_proxy_leave, 35 | ts_proxy_mouse, 36 | ts_proxy_button, 37 | ts_proxy_key, 38 | ts_proxy_wheel, 39 | ts_proxy_getclipboard, 40 | ts_proxy_setclipboard, 41 | }; 42 | 43 | typedef struct ts_display_proxy_event_t { 44 | uint32_t event : 8, flags : 8, down : 1; 45 | union { 46 | ts_display_p display; 47 | struct { 48 | long x : 12, y : 12; 49 | } mouse; 50 | uint8_t button; 51 | uint16_t key; 52 | struct { 53 | long wheel : 4, y : 14, x : 14; 54 | } wheel; 55 | ts_clipboard_p clipboard; 56 | } u; 57 | } ts_display_proxy_event_t, *ts_display_proxy_event_p; 58 | 59 | #include "fifo_declare.h" 60 | 61 | DECLARE_FIFO(ts_display_proxy_event_t, proxy_fifo, 32); 62 | 63 | typedef struct ts_display_proxy_driver_t { 64 | ts_display_driver_t driver; 65 | ts_display_driver_p slave; 66 | 67 | ts_signal_t signal; 68 | proxy_fifo_t fifo; 69 | ts_remote_t remote; 70 | } ts_display_proxy_driver_t, *ts_display_proxy_driver_p; 71 | 72 | 73 | ts_display_driver_p 74 | ts_display_proxy_driver( 75 | ts_mux_p mux, 76 | ts_display_driver_p driver ); 77 | 78 | #endif /* __TS_DISPLAY_PROXY_H___ */ 79 | -------------------------------------------------------------------------------- /src/ts_master.h: -------------------------------------------------------------------------------- 1 | /* 2 | ts_master.h 3 | 4 | Copyright 2011 Michel Pollet 5 | 6 | This file is part of touchstream. 7 | 8 | touchstream is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | touchstream is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with touchstream. If not, see . 20 | */ 21 | 22 | /* 23 | * The "master" structure holds a list of "display", and holds the "main" 24 | * mouse cursor, when the mouse is moved, it detects any boundary changes 25 | * and "activates" (ie "enter") the new active one while "leaving" the 26 | * previously active one. 27 | * 28 | * There is a convention that the first display in the list is the "main" one, 29 | * regarless of wether you are a server or a client. 30 | */ 31 | 32 | #ifndef __TS_MASTER_H___ 33 | #define __TS_MASTER_H___ 34 | 35 | #include "ts_display.h" 36 | 37 | typedef struct ts_master_t { 38 | int displayCount; 39 | ts_display_p display[8]; 40 | ts_display_p active; 41 | 42 | int mousex, mousey; 43 | } ts_master_t, *ts_master_p; 44 | 45 | void 46 | ts_master_init( 47 | ts_master_p master); 48 | 49 | void 50 | ts_master_display_add( 51 | ts_master_p master, 52 | ts_display_p d ); 53 | 54 | void 55 | ts_master_display_remove( 56 | ts_master_p master, 57 | ts_display_p d); 58 | 59 | ts_display_p 60 | ts_master_display_get( 61 | ts_master_p master, 62 | char * display); 63 | 64 | ts_display_p 65 | ts_master_display_get_for( 66 | ts_master_p master, 67 | int x, int y); 68 | 69 | ts_display_p 70 | ts_master_get_active( 71 | ts_master_p master); 72 | 73 | ts_display_p 74 | ts_master_get_main( 75 | ts_master_p master); 76 | 77 | int 78 | ts_master_set_active( 79 | ts_master_p master, 80 | ts_display_p d ); 81 | 82 | void 83 | ts_master_mouse_move( 84 | ts_master_p m, 85 | int dx, int dy ); 86 | 87 | #endif /* __TS_MASTER_H___ */ 88 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile 2 | # 3 | # Copyright 2011 Michel Pollet 4 | # 5 | # This file is part of touchstream. 6 | # 7 | # touchstream is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # touchstream is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with touchstream. If not, see . 19 | 20 | MARCH = $(shell $(CC) -dumpmachine|awk -F - '{print $$2;}') 21 | 22 | EXTRA_LDFLAGS += -lpthread 23 | 24 | VPATH = cmd 25 | VPATH += src 26 | 27 | IPATH += src 28 | 29 | SHARED_SRC = ${wildcard src/*.c} 30 | 31 | ifeq ($(MARCH),apple) 32 | CC = clang 33 | 34 | # This requires X11.app installed 35 | SHARED_SRC += ${wildcard src/xorg/*.c} 36 | VPATH += src/xorg 37 | EXTRA_CFLAGS += -I/usr/X11/include 38 | EXTRA_LDFLAGS += -L/usr/X11/lib -lX11 -lXtst -lXfixes -lXext 39 | 40 | SHARED_SRC += ${wildcard src/osx/*.c} 41 | VPATH += src/osx 42 | EXTRA_CFLAGS += -Wno-deprecated-declarations 43 | EXTRA_CFLAGS += -F/System/Library/Frameworks/Carbon.framework/Frameworks/ 44 | EXTRA_CFLAGS += -DCONFIG_OSX=1 45 | EXTRA_LDFLAGS += -framework Carbon 46 | 47 | else 48 | # Generic flags (aka linux) 49 | SHARED_SRC += ${wildcard src/xorg/*.c} 50 | VPATH += src/xorg 51 | EXTRA_CFLAGS += -DCONFIG_LINUX=1 52 | EXTRA_LDFLAGS += -lX11 -lXtst -lXfixes -lXext 53 | EXTRA_LDFLAGS += -Wl,--relax,--gc-sections 54 | endif 55 | 56 | EXTRA_CFLAGS += ${patsubst %,-I%,${subst :, ,${IPATH}}} 57 | 58 | 59 | all: touchstream 60 | 61 | include Makefile.common 62 | 63 | SHARED_OBJ := ${patsubst %, ${OBJ}/%, ${notdir ${SHARED_SRC:.c=.o}}} 64 | 65 | 66 | touchstream: ${OBJ} ${OBJ}/touchstream.bin 67 | @echo $@ Done 68 | 69 | ${OBJ}/touchstream.bin : ${OBJ}/touchstream.o 70 | ${OBJ}/touchstream.bin : ${SHARED_OBJ} 71 | 72 | 73 | install: all 74 | if [ -f $(DESTDIR)/bin/touchstream ]; then \ 75 | mv $(DESTDIR)/bin/touchstream $(DESTDIR)/bin/touchstream.old ; \ 76 | fi 77 | cp ${OBJ}/touchstream.bin $(DESTDIR)/bin/touchstream 78 | -------------------------------------------------------------------------------- /src/ts_signal.c: -------------------------------------------------------------------------------- 1 | /* 2 | * ts_signal.c 3 | * 4 | Copyright 2011 Michel Pollet 5 | 6 | This file is part of touchstream. 7 | 8 | touchstream is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | touchstream is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with touchstream. If not, see . 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | #include "ts_signal.h" 35 | 36 | 37 | int 38 | ts_signal_init( 39 | ts_signal_p res ) 40 | { 41 | if (socketpair(AF_UNIX, SOCK_DGRAM, 0, res->fd)) { 42 | perror("ts_signal_new socketpair"); 43 | return -1; 44 | } else { 45 | for (int i = 0; i < 2; i++) { 46 | int flags = fcntl(res->fd[i], F_GETFL, 0); 47 | fcntl(res->fd[i], F_SETFL, flags | O_NONBLOCK); 48 | } 49 | } 50 | return 0; 51 | } 52 | 53 | void 54 | ts_signal_dispose( 55 | ts_signal_p s) 56 | { 57 | if (!s) 58 | return; 59 | close(s->fd[0]); 60 | close(s->fd[1]); 61 | s->fd[1] = s->fd[0] = -1; 62 | } 63 | 64 | //! Signal the one side 65 | void 66 | ts_signal( 67 | ts_signal_p s, 68 | int end, 69 | uint8_t what ) 70 | { 71 | if (!what) what++; 72 | if (write(s->fd[end], &what, 1)) 73 | ; 74 | } 75 | 76 | void 77 | ts_signal_flush( 78 | ts_signal_p s, 79 | int end ) 80 | { 81 | uint8_t buf[32]; 82 | while (read(s->fd[end], buf, sizeof(buf)) == sizeof(buf)) 83 | ; 84 | } 85 | 86 | //! Signal wait 87 | uint8_t 88 | ts_signal_wait( 89 | ts_signal_p s, 90 | int end, 91 | uint32_t inTimeout ) 92 | { 93 | fd_set set; 94 | FD_ZERO(&set); 95 | FD_SET(s->fd[end], &set); 96 | struct timeval tm = { .tv_sec = 0, .tv_usec = inTimeout * 1000 }; 97 | int ret = select(s->fd[end]+1, &set, NULL, NULL, &tm); 98 | if (ret < 0) perror("vdmsg_funnel_wait_empty"); 99 | if (ret > 0) { 100 | uint8_t byte; 101 | if (read(s->fd[end], &byte, 1) > 0) { 102 | return byte; 103 | } 104 | } 105 | return 0; 106 | } 107 | -------------------------------------------------------------------------------- /src/xorg/ts_xorg_keymap.c: -------------------------------------------------------------------------------- 1 | /* 2 | ts_xorg_keymap.c 3 | 4 | Copyright 2011 Michel Pollet 5 | 6 | This file is part of touchstream. 7 | 8 | touchstream is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | touchstream is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with touchstream. If not, see . 20 | */ 21 | 22 | 23 | #include 24 | #include 25 | #include 26 | #include "ts_defines.h" 27 | #include "ts_keymap.h" 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | #include "ts_xorg.h" 34 | 35 | int 36 | ts_xorg_keymap_load( 37 | ts_xorg_krev_p map, 38 | Display * m_display) 39 | { 40 | // get the number of keycodes 41 | int minKeycode, maxKeycode; 42 | XDisplayKeycodes(m_display, &minKeycode, &maxKeycode); 43 | int numKeycodes = maxKeycode - minKeycode + 1; 44 | 45 | // printf("xorg_load_keymap min %04x max %04x\n", minKeycode, maxKeycode); 46 | 47 | // get the keyboard mapping for all keys 48 | int keysymsPerKeycode; 49 | KeySym* allKeysyms = XGetKeyboardMapping(m_display, 50 | minKeycode, numKeycodes, 51 | &keysymsPerKeycode); 52 | 53 | // printf("xorg_load_keymap min %p keysymsPerKeycode %d\n", allKeysyms, keysymsPerKeycode); 54 | 55 | for (int i = 0; i < numKeycodes; i++) { 56 | // printf("code %04x : ", minKeycode + i); 57 | KeySym * s = allKeysyms + (i * keysymsPerKeycode); 58 | int k = 0; 59 | if (!s[k]) 60 | continue; 61 | //printf("%08x ", (int)s[k]); 62 | int b = s[k] & 0xff; 63 | if (map->map[b].count == 4) { 64 | printf("%s overflow map %02x\n", __func__, (int)s[k] & 0xff); 65 | } else { 66 | map->map[b].v[map->map[b].count].code = minKeycode + i; 67 | map->map[b].v[map->map[b].count].key = s[k]; 68 | map->map[b].count++; 69 | } 70 | 71 | //printf("\n"); 72 | } 73 | XFree(allKeysyms); 74 | return 0; 75 | } 76 | 77 | int 78 | ts_xorg_key_to_button( 79 | ts_xorg_krev_p map, 80 | uint32_t key) 81 | { 82 | int b = key & 0xff; 83 | for (int i = 0; i < map->map[key & 0xff].count; i++) 84 | if (map->map[b].v[i].key == key) { 85 | // printf("%s mapped %04x to button %02x\n",__func__, key, map->map[b].v[i].code); 86 | return map->map[b].v[i].code; 87 | } 88 | return key; 89 | } 90 | -------------------------------------------------------------------------------- /doc/tags_to_dot.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | # 3 | # Copyright 2008, 2009 Michel Pollet 4 | # 5 | # This file is part of simavr. 6 | # 7 | # simavr is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # simavr is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with simavr. If not, see . 19 | 20 | $files = Hash.new 21 | $syms = Hash.new 22 | 23 | tags = File.new(ARGV[0]) 24 | 25 | key = Array.new 26 | 27 | while !tags.eof? 28 | next unless tags.readline.chomp.match(/([^\t]+)\t([^\t]+)\t(.*)\$\/;"\t([^\t]+)/); 29 | key[0] = $1; 30 | key[1] = $2; 31 | key[3] = $3; 32 | key[4] = $4; 33 | 34 | next if key[4] == 'm' or key[4] == 't' or key[4] == 's' or key[4] == 'e' or key[4] == 'v'; 35 | next if key[0].match(/[us]?int[0-9]+_t/); 36 | next if key[0] == "ROM_BASED"; 37 | 38 | key[1].gsub!(/.*\/|\.[ch]$/,""); 39 | 40 | unless $files.key? key[1] 41 | $files[key[1]] = Hash.new 42 | end 43 | unless $files[key[1]].key? key[0] 44 | $files[key[1]][key[0]] = Hash.new 45 | $syms[key[0]] = key[1] 46 | end 47 | #puts key[0] + " = '#{key[4]}'" 48 | end 49 | 50 | puts "digraph dump { node [shape=rect]; compound=true; nodesep=.1; ranksep=2; rankdir=LR; concentrate=true; " 51 | 52 | modules = Hash.new; 53 | links = Array.new; 54 | 55 | 1.upto(ARGV.length-1) { |i| 56 | 57 | use = File.new(ARGV[i]) 58 | # puts "<<<<<<<>>" + line 69 | words = line.split(/[ \t]+/); 70 | words.each { |w| 71 | if $syms.key? w and $syms[w] != fil 72 | unless $files[$syms[w]][w].key? fil 73 | # puts w + " is in " + $syms[w] 74 | $files[$syms[w]][w][fil] = 1 75 | 76 | sym=w 77 | unless modules.key? fil 78 | modules[fil] = Array.new 79 | end 80 | modules[fil] += [ "\"cc_#{fil}_#{sym}\" [label=\"#{sym}\",color=\"gray\",height=\".08\",style=dotted];" ] 81 | links += ["\"cc_#{fil}_#{sym}\" -> \"dd_#{$syms[w]}_#{sym}\";"] 82 | end 83 | end 84 | } 85 | end 86 | } 87 | 88 | $files.keys.each { |fil| 89 | # puts "File #{fil} ?" 90 | $files[fil].keys.each { |sym| 91 | # puts "\tSym #{sym} : #{$files[fil][sym].length} ?" 92 | if $files[fil][sym].length > 0 93 | unless modules.key? fil 94 | modules[fil] = Array.new 95 | end 96 | modules[fil] += [ "\"dd_#{fil}_#{sym}\" [label=\"#{sym}\"];" ] 97 | end 98 | } 99 | } 100 | modules.keys.each {|fil| 101 | puts "\tsubgraph cluster_#{fil} {\n\t\tlabel=\"#{fil}\"; fontsize=\"18\"; " 102 | modules[fil].each { |lin| 103 | puts "\t\t#{lin}" 104 | } 105 | puts "\t}" 106 | } 107 | links.each { |lin| 108 | puts "\t#{lin}" 109 | } 110 | puts "}" 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | touchstream - mouse/keyboard network sharing utility 2 | (C) Michel Pollet 3 | 4 | Introduction 5 | -------- 6 | _touchstream_ is a program that allows you to share a keyboard & mouse over 7 | a number of machines, on a LAN, typicaly. 8 | 9 | Imagine having a mac and a linux machine, with their own screens; you 10 | can use the Mac mouse and keyboard on the linux box too by moving the 11 | mouse pointer to a max screen edge, and it starts moving on the linux screen. 12 | 13 | Also, the clipboard is exchanged as you switch back & forth. 14 | 15 | 16 | Rationale 17 | -------- 18 | The goal of _touchstream_ was to replace synergy -- at least on my desk. 19 | Synergy has various issues, notably on the bloat department; "powertop" 20 | flags it as one of the most used/busy process on the machine, even tho 21 | it technicaly hasn't a lot to do, furthermore the codebase is bloated 22 | too, and the implementation uses countless locks, mutexes and other 23 | distasteful things that adds a lot of overhead, and that one doesn't 24 | strictly need if designing the execution path properly. 25 | 26 | The other reason is that synergy stopped working on my configuration 27 | recently for me, and I realised about it's code bloat when I went to 28 | try to fix it. 29 | 30 | Obviously, quite a bit of the platform specific bits of touchstream 31 | are copied straight out of synergy's source code, and not only deserve 32 | the credit, but also share their GPL licence. In many cases, the code 33 | has been entirely reworked tho, and I haven't taken care of copying 34 | the attribution and copyrights for individual snipets. 35 | 36 | The actualy "core" of touchstream is entirely original, and thats 37 | why the files uses my generic GPL header bits. 38 | 39 | Status 40 | -------- 41 | Anyway, touchstream is C99, doesn't use a single mutex, and works for 42 | now on OSX as a server (read, the screen that has the keyboard and mouse) 43 | and as a client of a recent xorg. 44 | 45 | It also can work without a "client" process by talking directly to 46 | remote xorg servers, as long as 1) the server allows tcp connections, 47 | and 2) you have disabled access control (xhost +). 48 | 49 | Clearly it lacks some of the features synergy has (windows support, 50 | non-latin keyboard support etc) but it also a LOT simpler, and uses 51 | a fraction of the resources. 52 | 53 | ## Requirements 54 | 55 | In order to build touchstream for your platform you will need X11 headers and a basic C toolchain. 56 | 57 | ### Mac OS X 58 | For OS X 10.14 you need: 59 | * XQuartz 60 | * Xcode command line tools 61 | 62 | ### Linux 63 | * Compiles on ubuntu 16.04 with the following packages installed: 64 | * gcc (build-essential) 65 | * libx11-dev 66 | * x11proto-core-dev 67 | * libxtst-dev 68 | * libxext-dev 69 | 70 | ### Building 71 | 72 | With the prerequisites installed you should be able to simply run `make`. 73 | 74 | 75 | ## Usage 76 | 77 | Running `touchstream.bin` 78 | 79 | ### Common command arguments 80 | 81 | > `-v` verbose output (repeat for more verbose) 82 | > `-D` daemonize 83 | 84 | ### Server 85 | 86 | > `-s` run touchstream server 87 | 88 | ```bash 89 | # specify -s followed by a resolvable name for the server. 90 | # Use the same name when firing up the client (see below) 91 | touchstream.bin -s server-name.local 92 | ``` 93 | 94 | ### Client 95 | 96 | > `-c` run couchstream client 97 | 98 | ```bash 99 | # Specify the server name and direction like this 100 | # -c server-name=[left|right|top|bottom] 101 | # screen to attach to this client. E.g. if Client is left of 102 | # server then specify "left" after the equal: 103 | 104 | touchstream.bin -c server-name.local=left 105 | ``` -------------------------------------------------------------------------------- /src/ts_mux.h: -------------------------------------------------------------------------------- 1 | /* 2 | ts_mux.h 3 | 4 | Copyright 2011 Michel Pollet 5 | 6 | This file is part of touchstream. 7 | 8 | touchstream is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | touchstream is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with touchstream. If not, see . 20 | */ 21 | 22 | /* 23 | * a "mux" is the keystone of the whole system, allowing to serialize 24 | * all the events for the displays into one single thread, thus preventing 25 | * the need for mutexes etc. 26 | * The 'mux' uses 'remote' small structure that are associated with a socket, 27 | * and can also have as many callbacks to customize it's behaviour. 28 | * 29 | * There are a few types of "remotes" 30 | * + The "listen" remote is just a listen socket waiting for incoming 31 | * connections, when one is received, it creates a "data" remote with it 32 | * + A "connect" remote is an outgoing connection, it will behave mostly 33 | * as a "data" socket when it has been established 34 | * + A "xorg" remote is what is used by the xorg "display" subsystem to 35 | * serialize the reception of events via XPending(). See ts_xorg_client.[ch] 36 | * + A "proxy" remote is defined in ts_display_proxy.[ch], it it's just 37 | * a signaling socket that allows that proxy to empty it's fifo and make 38 | * it's calls to a "slace" display in the mux's thread. 39 | */ 40 | #ifndef __TS_MUX_H___ 41 | #define __TS_MUX_H___ 42 | 43 | #include 44 | #include "ts_display.h" 45 | #include "ts_master.h" 46 | #include "ts_signal.h" 47 | 48 | /* 49 | * "macro" states for remote connections/sockets 50 | */ 51 | enum { 52 | skt_state_None = 0, 53 | skt_state_Listen, 54 | skt_state_Connect, 55 | skt_state_Data, 56 | }; 57 | 58 | struct ts_display_proxy_driver_t; 59 | /* 60 | * a ts_remote_t handles one connection for the mux. They can be 61 | * listen remotes, data (accepted) remotes, connect (outgoing) 62 | * remotes, and even xorg ConnectionNumber etc 63 | * Any filedescriptor will do 64 | */ 65 | struct ts_mux_t; 66 | typedef struct ts_remote_t { 67 | struct ts_mux_t * mux; 68 | ts_display_p display; 69 | int socket; 70 | int state; 71 | struct sockaddr_in addr; 72 | int accept_socket; 73 | time_t timeout; 74 | 75 | struct ts_display_proxy_driver_t * proxy; 76 | 77 | int in_len; 78 | int in_size; 79 | uint8_t * in; 80 | 81 | int out_len; 82 | int out_size; 83 | uint8_t * out; 84 | 85 | int (*start)(struct ts_remote_t * remote); 86 | int (*restart)(struct ts_remote_t * remote); 87 | int (*dispose)(struct ts_remote_t * remote); 88 | 89 | int (*can_read)(struct ts_remote_t * remote); 90 | int (*data_read)(struct ts_remote_t * remote); 91 | int (*can_write)(struct ts_remote_t * remote); 92 | int (*data_write)(struct ts_remote_t * remote); 93 | 94 | } ts_remote_t, *ts_remote_p; 95 | 96 | /* 97 | * A mux handles up to 32 remote 98 | */ 99 | typedef struct ts_mux_t { 100 | ts_master_p master; 101 | pthread_t thread; 102 | 103 | ts_signal_t signal; 104 | uint32_t dp_usage; 105 | ts_remote_p dp[32]; 106 | } ts_mux_t, *ts_mux_p; 107 | 108 | /* 109 | * This is called by ts_mux_port_new() 110 | */ 111 | int 112 | ts_mux_start( 113 | ts_mux_p mux, 114 | ts_master_p master ); 115 | 116 | int 117 | ts_mux_port_new( 118 | ts_mux_p mux, 119 | ts_master_p master, 120 | char * address, 121 | ts_display_p display); 122 | 123 | void 124 | ts_mux_signal( 125 | ts_mux_p mux, 126 | uint8_t what ); 127 | 128 | int 129 | ts_mux_register( 130 | ts_remote_p r ); 131 | int 132 | ts_mux_unregister( 133 | ts_remote_p r); 134 | 135 | #endif /* __TS_MUX_H___ */ 136 | -------------------------------------------------------------------------------- /cmd/touchstream.c: -------------------------------------------------------------------------------- 1 | /* 2 | touchstream.c 3 | 4 | Copyright 2011 Michel Pollet 5 | 6 | This file is part of touchstream. 7 | 8 | touchstream is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | touchstream is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with touchstream. If not, see . 20 | */ 21 | 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "ts_defines.h" 34 | #include "ts_mux.h" 35 | #include "ts_verbose.h" 36 | 37 | int verbose = 0; 38 | 39 | void V1(const char * format, ...) 40 | { 41 | if (verbose < 1) return; 42 | va_list va; 43 | va_start(va, format); 44 | vprintf(format, va); 45 | va_end(va); 46 | } 47 | void V2(const char * format, ...) 48 | { 49 | if (verbose < 2) return; 50 | va_list va; 51 | va_start(va, format); 52 | vprintf(format, va); 53 | va_end(va); 54 | } 55 | void V3(const char * format, ...) 56 | { 57 | if (verbose < 3) return; 58 | va_list va; 59 | va_start(va, format); 60 | vprintf(format, va); 61 | va_end(va); 62 | } 63 | 64 | /* 65 | * These globals are "special", they are exported, and 66 | * are set by platform specific code in a __attribute__((constructor)) 67 | * function. 68 | */ 69 | ts_platform_create_callback_p ts_platform_create_server = NULL; 70 | ts_platform_create_callback_p ts_platform_create_client = NULL; 71 | ts_platform_create_callback_p ts_xorg_create_client = NULL; 72 | 73 | ts_mux_t mux[1]; 74 | ts_master_t master[1]; 75 | 76 | int 77 | main( 78 | int argc, 79 | char * argv[]) 80 | { 81 | int server = 1; 82 | int dae = 0; 83 | char * client = NULL; 84 | char * param = NULL; 85 | char * xorg[8] = {0}; 86 | int xorgCount = 0; 87 | 88 | for (int i = 1; i < argc; i++) { 89 | if (!strcmp(argv[i], "-s")) { 90 | server++; 91 | } else if (!strcmp(argv[i], "-D")) { 92 | dae++; 93 | } else if (!strncmp(argv[i], "-v", 2)) { 94 | if (isdigit(argv[i][2])) 95 | verbose = argv[i][2] - '0'; 96 | else 97 | verbose++; 98 | V1("Set verbose to %d\n", verbose); 99 | } else if (!strcmp(argv[i], "-x") && i < argc-1) { 100 | if (!ts_xorg_create_client) { 101 | fprintf(stderr, "%s: xorg client mode unsupported on this platform\n", 102 | basename(argv[0])); 103 | exit(1); 104 | } 105 | xorg[xorgCount++] = argv[++i]; 106 | } else if (!strcmp(argv[i], "-c") && i < argc-1) { 107 | i++; 108 | param = argv[i]; 109 | char * name = strsep(¶m, "="); 110 | char * col = strchr(name, ':'); 111 | if (col) *col = 0; 112 | if (gethostbyname(name)) { 113 | client = argv[i]; // with : 114 | server = 0; 115 | V1("%s client host: '%s'\n", argv[0], name); 116 | } 117 | } 118 | } 119 | 120 | if (dae) { 121 | char *xa = getenv("XAUTHORITY"); 122 | V1("xa = %s\n", xa); 123 | // daemon() somehow screws the clipboard at least on osx, so we use 124 | // the roughly 'equivalent' code, enough to be detached from controling 125 | // terminal etc. Also open a log in /tmp, if in verbose mode 126 | //daemon(0, 0); 127 | if (fork()) 128 | exit(0); 129 | if (chdir("/tmp")) 130 | ; 131 | close(0);close(1);close(2); 132 | if (verbose) { 133 | int fd = open("/tmp/touchstream.log", O_WRONLY|O_CREAT, 0644); 134 | dup2 (fd, 0); 135 | dup2 (fd, 1); 136 | dup2 (fd, 2); 137 | if (fd > 2) 138 | close(fd); 139 | if (xa) { 140 | char envv[256]; 141 | sprintf(envv, "XAUTHORITY=%s", xa); 142 | putenv(envv); 143 | } 144 | if (system("pwd;env")) 145 | ; 146 | } 147 | } 148 | 149 | ts_master_init(master); 150 | 151 | ts_platform_create_callback_p platform = NULL; 152 | 153 | if (server) { 154 | if (ts_platform_create_server) 155 | platform = ts_platform_create_server; 156 | else { 157 | fprintf(stderr, "%s: server mode unsupported on this platform\n", 158 | basename(argv[0])); 159 | exit(1); 160 | } 161 | } else { 162 | if (ts_platform_create_client) 163 | platform = ts_platform_create_client; 164 | else { 165 | fprintf(stderr, "%s: client mode unsupported on this platform\n", 166 | basename(argv[0])); 167 | exit(1); 168 | } 169 | } 170 | ts_display_p main_display = platform(mux, master, param); 171 | 172 | ts_mux_port_new(mux, master, client, main_display); 173 | 174 | for (int i = 0; i < xorgCount; i++) 175 | ts_xorg_create_client(mux, master, xorg[i]); 176 | 177 | ts_display_run(main_display); 178 | } 179 | -------------------------------------------------------------------------------- /src/ts_master.c: -------------------------------------------------------------------------------- 1 | /* 2 | ts_master.c 3 | 4 | Copyright 2011 Michel Pollet 5 | 6 | This file is part of touchstream. 7 | 8 | touchstream is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | touchstream is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with touchstream. If not, see . 20 | */ 21 | 22 | 23 | #include 24 | #include 25 | #include 26 | #include "ts_defines.h" 27 | #include "ts_verbose.h" 28 | 29 | void 30 | ts_master_init( 31 | ts_master_p master) 32 | { 33 | memset(master, 0, sizeof(*master)); 34 | } 35 | 36 | void 37 | ts_master_display_add( 38 | ts_master_p master, 39 | ts_display_p d) 40 | { 41 | for (int i = 0; i < master->displayCount; i++) 42 | if (master->display[i] == d) 43 | return; 44 | 45 | master->display[master->displayCount++] = d; 46 | d->master = master; 47 | if (master->displayCount == 1) 48 | ts_master_set_active(master, d); 49 | } 50 | 51 | void 52 | ts_master_display_remove( 53 | ts_master_p master, 54 | ts_display_p d) 55 | { 56 | for (int i = 0; i < master->displayCount && d; i++) 57 | if (master->display[i] == d) { 58 | memmove(master->display + i, 59 | master->display + i + 1, 60 | (master->displayCount - i - 1) * sizeof(ts_display_p)); 61 | master->displayCount--; 62 | if (master->active == d) { 63 | if (master->displayCount && d != master->display[0]) 64 | ts_master_set_active(master, master->display[0]); 65 | else 66 | ts_master_set_active(master, NULL); 67 | } 68 | ts_display_dispose(d); 69 | break; 70 | } 71 | } 72 | 73 | ts_display_p 74 | ts_master_display_get( 75 | ts_master_p master, 76 | char * display ) 77 | { 78 | if (!master) 79 | return NULL; 80 | for (int i = 0; i < master->displayCount; i++) 81 | if (!strcmp(master->display[i]->name, display)) 82 | return master->display[i]; 83 | return NULL; 84 | } 85 | 86 | ts_display_p 87 | ts_master_display_get_for( 88 | ts_master_p master, 89 | int x, int y) 90 | { 91 | // first check the 'active' screen, it's probably a match anyway, 92 | // then check the main one, since it's very like to be that if not, 93 | // lastly, check all the others 94 | if (ts_ptinrect(&master->active->bounds, x, y)) 95 | return master->active; 96 | else if (master->active != master->display[0] && 97 | ts_ptinrect(&master->display[0]->bounds, x, y)) 98 | return master->display[0]; 99 | else { 100 | for (int i = 1 /* we did zero */; i < master->displayCount; i++) 101 | if (ts_ptinrect(&master->display[i]->bounds, x, y)) 102 | return master->display[i]; 103 | } 104 | return NULL; 105 | } 106 | 107 | ts_display_p 108 | ts_master_get_active( 109 | ts_master_p master ) 110 | { 111 | return master->active; 112 | } 113 | 114 | ts_display_p 115 | ts_master_get_main( 116 | ts_master_p master ) 117 | { 118 | return master->displayCount ? master->display[0] : NULL; 119 | } 120 | 121 | 122 | int 123 | ts_master_set_active( 124 | ts_master_p master, 125 | ts_display_p d ) 126 | { 127 | if (!master) 128 | return -1; 129 | 130 | if (master->active == d) 131 | return 0; 132 | ts_display_p old = master->active; 133 | if (old) 134 | ts_display_leave(old); 135 | master->active = d ? d : master->displayCount ? master->display[0] : NULL; 136 | if (master->active) { 137 | V2("%s %s\n", __func__, master->active->name); 138 | ts_display_enter(master->active); 139 | // get clipboard is asynchronous, it's it's job to decide 140 | // to set the clipboard when it eventualy gets one 141 | if (old) 142 | ts_display_getclipboard(old, master->active); 143 | } 144 | return 0; 145 | } 146 | 147 | void 148 | ts_master_mouse_move( 149 | ts_master_p m, 150 | int dx, int dy ) 151 | { 152 | int wasedge = ts_ptonedge(&m->active->bounds, m->mousex, m->mousey); 153 | int nx = m->mousex + dx; 154 | int ny = m->mousey + dy; 155 | 156 | ts_display_p newd = ts_master_display_get_for(m, nx, ny); 157 | /* 158 | * If we're outside of any screen, clip the coordinates to the active one 159 | */ 160 | if (!newd || !newd->moved) 161 | newd = m->active; 162 | 163 | /* 164 | * If we are on the active screen, watch for the mouse arriving on an edge 165 | */ 166 | if (newd == m->active) { 167 | int isedge = ts_ptonedge(&m->active->bounds, nx, ny); 168 | if (!wasedge && isedge) { 169 | V2("%s edge detected\n", __func__); 170 | // offset the coordinates to see if there is a screen 'over' the edge 171 | ts_pt_out_edge(&m->active->bounds, &nx, &ny); 172 | ts_display_p stick = ts_master_display_get_for(m, nx, ny); 173 | if (stick) 174 | newd = stick; 175 | } 176 | } 177 | 178 | ts_clippt(&newd->bounds, &nx, &ny); 179 | dx = nx - m->mousex; 180 | dy = ny - m->mousey; 181 | m->mousex = nx; 182 | m->mousey = ny; 183 | 184 | // V3("%s %5d %5d\n", __func__, m->mousex, m->mousey); 185 | 186 | // move the mouse before switching target, since enter() resets the mouse 187 | ts_display_movemouse(m->active, dx, dy); 188 | if (newd != m->active) 189 | ts_master_set_active(m, newd); 190 | 191 | } 192 | -------------------------------------------------------------------------------- /src/ts_display.c: -------------------------------------------------------------------------------- 1 | /* 2 | ts_display.c 3 | 4 | Copyright 2011 Michel Pollet 5 | 6 | This file is part of touchstream. 7 | 8 | touchstream is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | touchstream is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with touchstream. If not, see . 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include "ts_defines.h" 26 | #include "ts_verbose.h" 27 | 28 | void 29 | ts_display_init( 30 | ts_display_p d, 31 | struct ts_master_t * master, 32 | ts_display_driver_p driver, 33 | char * name, 34 | char * param ) 35 | { 36 | memset(d, 0, sizeof(*d)); 37 | d->master = master; 38 | d->driver = driver; 39 | d->name = strdup(name); 40 | d->param = param ? strdup(param) : NULL; 41 | if (d->driver && d->driver->init) 42 | d->driver->init(d); 43 | V1("Creating display '%s' (%s)\n", name, __func__); 44 | } 45 | 46 | void 47 | ts_display_dispose( 48 | ts_display_p d ) 49 | { 50 | if (d->name) 51 | free(d->name); 52 | if (d->param) 53 | free(d->param); 54 | d->param = NULL; 55 | d->name = NULL; 56 | d->master = NULL; 57 | if (d->driver && d->driver->dispose) 58 | d->driver->dispose(d); 59 | } 60 | 61 | ts_display_driver_p 62 | ts_display_clone_driver( 63 | ts_display_driver_p driver ) 64 | { 65 | ts_display_driver_p res = malloc(sizeof(*driver)); 66 | *res = *driver; 67 | res->_mutable = 1; 68 | return res; 69 | } 70 | 71 | void 72 | ts_display_run( 73 | ts_display_p d ) 74 | { 75 | if (d && d->driver && d->driver->run) 76 | d->driver->run(d); 77 | } 78 | 79 | void 80 | ts_display_enter( 81 | ts_display_p d ) 82 | { 83 | if (!d) 84 | return; 85 | d->moved = 0; 86 | d->mousex = d->master->mousex - d->bounds.x; 87 | d->mousey = d->master->mousey - d->bounds.y; 88 | V2("Entering display %s at %5d %5d (%s)\n", d->name, d->mousex, d->mousey, __func__); 89 | if (d->driver && d->driver->enter) 90 | d->driver->enter(d); 91 | d->active = 1; 92 | } 93 | 94 | void 95 | ts_display_leave( 96 | ts_display_p d ) 97 | { 98 | if (!d) 99 | return; 100 | V3("Leaving display %s at %5d %5d\n", d->name, d->mousex, d->mousey, __func__); 101 | if (d->driver && d->driver->leave) 102 | d->driver->leave(d); 103 | d->active = 0; 104 | } 105 | 106 | int 107 | ts_display_place( 108 | ts_display_p main, 109 | ts_display_p which, 110 | char * where ) 111 | { 112 | if (!which || !where) 113 | return -1; 114 | if (!main) 115 | main = ts_master_get_main(which->master); 116 | if (!main) 117 | return -1; 118 | //ts_rect_t m = main->bounds; 119 | 120 | V1("Placing %s %s of %s (%s)\n", which->name, where, main->name, __func__); 121 | if (!strcmp(where, "right")) { 122 | which->bounds.x = main->bounds.x + main->bounds.w; 123 | which->bounds.y = main->bounds.y; 124 | } else if (!strcmp(where, "top")) { 125 | which->bounds.x = main->bounds.x; 126 | which->bounds.y = main->bounds.y - which->bounds.h; 127 | } else if (!strcmp(where, "left")) { 128 | which->bounds.x = main->bounds.y - which->bounds.w; 129 | which->bounds.y = main->bounds.y; 130 | } else if (!strcmp(where, "bottom")) { 131 | which->bounds.x = main->bounds.x; 132 | which->bounds.y = main->bounds.y + main->bounds.h; 133 | } else { 134 | printf("%s unsupported place '%s'\n", __func__, where); 135 | } 136 | ts_rect_t w = which->bounds; 137 | V2("%s %s %d,%d %dx%d\n", __func__, which->name, w.x, w.y, w.w, w.h); 138 | return 0; 139 | } 140 | 141 | 142 | int 143 | ts_display_movemouse( 144 | ts_display_p d, 145 | int dx, int dy ) 146 | { 147 | if (!d) 148 | return -1; 149 | if (dx || dy) 150 | d->moved = 1; 151 | d->mousex += dx; 152 | d->mousey += dy; 153 | if (d->driver && d->driver->mouse) 154 | d->driver->mouse(d, dx, dy); 155 | return 0; 156 | } 157 | 158 | void 159 | ts_display_button( 160 | ts_display_p d, 161 | int button, int down ) 162 | { 163 | if (d && d->driver && d->driver->button) 164 | d->driver->button(d, button, down); 165 | } 166 | 167 | void 168 | ts_display_wheel( 169 | ts_display_p d, 170 | int wheel, int x, int y ) 171 | { 172 | if (d && d->driver && d->driver->wheel) 173 | d->driver->wheel(d, wheel, x, y); 174 | } 175 | 176 | void 177 | ts_display_key( 178 | ts_display_p d, 179 | uint16_t key, int down ) 180 | { 181 | if (d && d->driver && d->driver->key) 182 | d->driver->key(d, key, down); 183 | } 184 | 185 | void 186 | ts_display_getclipboard( 187 | ts_display_p d, 188 | ts_display_p to) 189 | { 190 | //printf("ts_display_getclipboard %p %s\n", d, d ? d->name : ""); 191 | if (d && d->driver && d->driver->getclipboard) 192 | d->driver->getclipboard(d, to); 193 | } 194 | 195 | void 196 | ts_display_setclipboard( 197 | ts_display_p d, 198 | ts_clipboard_p clipboard ) 199 | { 200 | //printf("ts_display_setclipboard %p %p %s driver %p setclipboard %p\n", 201 | // d, clipboard, d ? d->name : "", d->driver, 202 | // d->driver ? d->driver->setclipboard : NULL); 203 | if (d && d->driver && d->driver->setclipboard) 204 | d->driver->setclipboard(d, clipboard); 205 | } 206 | 207 | -------------------------------------------------------------------------------- /src/fifo_declare.h: -------------------------------------------------------------------------------- 1 | /* 2 | fido_declare.h 3 | Copyright (C) 2003-2012 Michel Pollet 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Lesser General Public 7 | License as published by the Free Software Foundation; either 8 | version 2.1 of the License, or (at your option) any later version. 9 | 10 | This library 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 | Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public 16 | License along with this library; if not, write to the Free Software 17 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | 20 | /* 21 | * FIFO helpers, aka circular buffers 22 | * 23 | * these macros define accessories for FIFOs of any name, type and 24 | * any (power of two) size 25 | */ 26 | 27 | #ifndef __FIFO_DECLARE__ 28 | #define __FIFO_DECLARE__ 29 | 30 | #ifdef __cplusplus 31 | extern "C" { 32 | #endif 33 | 34 | /* 35 | doing a : 36 | DECLARE_FIFO(uint8_t, myfifo, 128); 37 | 38 | will declare : 39 | enum : myfifo_overflow_f 40 | type : myfifo_t 41 | functions: 42 | // write a byte into the fifo, return 1 if there was room, 0 if there wasn't 43 | int myfifo_write(myfifo_t *c, uint8_t b); 44 | // reads a byte from the fifo, return 0 if empty. Use myfifo_isempty() to check beforehand 45 | uint8_t myfifo_read(myfifo_t *c); 46 | int myfifo_isfull(myfifo_t *c); 47 | int myfifo_isempty(myfifo_t *c); 48 | // returns number of items to read now 49 | uint16_t myfifo_get_read_size(myfifo_t *c); 50 | // read item at offset o from read cursor, no cursor advance 51 | uint8_t myfifo_read_at(myfifo_t *c, uint16_t o); 52 | // write b at offset o compared to current write cursor, no cursor advance 53 | void myfifo_write_at(myfifo_t *c, uint16_t o, uint8_t b); 54 | 55 | In your .c you need to 'implement' the fifo: 56 | DEFINE_FIFO(uint8_t, myfifo) 57 | 58 | To use the fifo, you must declare at least one : 59 | myfifo_t fifo = FIFO_NULL; 60 | 61 | while (!myfifo_isfull(&fifo)) 62 | myfifo_write(&fifo, 0xaa); 63 | .... 64 | while (!myfifo_isempty(&fifo)) 65 | b = myfifo_read(&fifo); 66 | */ 67 | 68 | #include 69 | 70 | #if __AVR__ 71 | #define FIFO_CURSOR_TYPE uint8_t 72 | #define FIFO_BOOL_TYPE char 73 | #define FIFO_INLINE 74 | #define FIFO_SYNC 75 | #endif 76 | 77 | #ifndef FIFO_CURSOR_TYPE 78 | #define FIFO_CURSOR_TYPE uint16_t 79 | #endif 80 | #ifndef FIFO_BOOL_TYPE 81 | #define FIFO_BOOL_TYPE int 82 | #endif 83 | #ifndef FIFO_INLINE 84 | #define FIFO_INLINE inline 85 | #endif 86 | 87 | /* We should not need volatile */ 88 | #ifndef FIFO_VOLATILE 89 | #define FIFO_VOLATILE 90 | #endif 91 | #ifndef FIFO_SYNC 92 | #define FIFO_SYNC __sync_synchronize() 93 | #endif 94 | 95 | #ifndef FIFO_ZERO_INIT 96 | #define FIFO_ZERO_INIT {0} 97 | #endif 98 | #define FIFO_NULL { FIFO_ZERO_INIT, 0, 0, 0 } 99 | 100 | /* New compilers don't like unused static functions. However, 101 | * we do like 'static inlines' for these small accessors, 102 | * so we mark them as 'unused'. It stops it complaining */ 103 | #ifdef __GNUC__ 104 | #define FIFO_DECL static __attribute__ ((unused)) 105 | #else 106 | #define FIFO_DECL static 107 | #endif 108 | 109 | #define DECLARE_FIFO(__type, __name, __size) \ 110 | enum { __name##_overflow_f = (1 << 0) }; \ 111 | enum { __name##_fifo_size = (__size) }; \ 112 | typedef struct __name##_t { \ 113 | __type buffer[__name##_fifo_size]; \ 114 | FIFO_VOLATILE FIFO_CURSOR_TYPE read; \ 115 | FIFO_VOLATILE FIFO_CURSOR_TYPE write; \ 116 | FIFO_VOLATILE uint8_t flags; \ 117 | } __name##_t 118 | 119 | #define DEFINE_FIFO(__type, __name) \ 120 | FIFO_DECL FIFO_INLINE FIFO_BOOL_TYPE __name##_write(__name##_t * c, __type b)\ 121 | {\ 122 | FIFO_CURSOR_TYPE now = c->write;\ 123 | FIFO_CURSOR_TYPE next = (now + 1) & (__name##_fifo_size-1);\ 124 | if (c->read != next) { \ 125 | c->buffer[now] = b;\ 126 | FIFO_SYNC; \ 127 | c->write = next;\ 128 | return 1;\ 129 | }\ 130 | return 0;\ 131 | }\ 132 | FIFO_DECL FIFO_INLINE FIFO_BOOL_TYPE __name##_isfull(__name##_t *c)\ 133 | {\ 134 | FIFO_CURSOR_TYPE next = (c->write + 1) & (__name##_fifo_size-1);\ 135 | return c->read == next;\ 136 | }\ 137 | FIFO_DECL FIFO_INLINE FIFO_BOOL_TYPE __name##_isempty(__name##_t * c)\ 138 | {\ 139 | return c->read == c->write;\ 140 | }\ 141 | FIFO_DECL FIFO_INLINE __type __name##_read(__name##_t * c)\ 142 | {\ 143 | __type res = FIFO_ZERO_INIT; \ 144 | FIFO_CURSOR_TYPE read = c->read;\ 145 | if (read == c->write)\ 146 | return res;\ 147 | res = c->buffer[read];\ 148 | FIFO_SYNC; \ 149 | c->read = (read + 1) & (__name##_fifo_size-1);\ 150 | return res;\ 151 | }\ 152 | FIFO_DECL FIFO_INLINE FIFO_CURSOR_TYPE __name##_get_read_size(__name##_t *c)\ 153 | {\ 154 | return ((c->write + __name##_fifo_size) - c->read) & (__name##_fifo_size-1);\ 155 | }\ 156 | FIFO_DECL FIFO_INLINE FIFO_CURSOR_TYPE __name##_get_write_size(__name##_t *c)\ 157 | {\ 158 | return (__name##_fifo_size-1) - __name##_get_read_size(c);\ 159 | }\ 160 | FIFO_DECL FIFO_INLINE void __name##_read_offset(__name##_t *c, FIFO_CURSOR_TYPE o)\ 161 | {\ 162 | FIFO_SYNC; \ 163 | c->read = (c->read + o) & (__name##_fifo_size-1);\ 164 | }\ 165 | FIFO_DECL FIFO_INLINE __type __name##_read_at(__name##_t *c, FIFO_CURSOR_TYPE o)\ 166 | {\ 167 | return c->buffer[(c->read + o) & (__name##_fifo_size-1)];\ 168 | }\ 169 | FIFO_DECL FIFO_INLINE void __name##_write_at(__name##_t *c, FIFO_CURSOR_TYPE o, __type b)\ 170 | {\ 171 | c->buffer[(c->write + o) & (__name##_fifo_size-1)] = b;\ 172 | }\ 173 | FIFO_DECL FIFO_INLINE void __name##_write_offset(__name##_t *c, FIFO_CURSOR_TYPE o)\ 174 | {\ 175 | FIFO_SYNC; \ 176 | c->write = (c->write + o) & (__name##_fifo_size-1);\ 177 | }\ 178 | FIFO_DECL FIFO_INLINE void __name##_reset(__name##_t *c)\ 179 | {\ 180 | FIFO_SYNC; \ 181 | c->read = c->write = c->flags = 0;\ 182 | }\ 183 | struct __name##_t 184 | 185 | #ifdef __cplusplus 186 | }; 187 | #endif 188 | 189 | #endif 190 | -------------------------------------------------------------------------------- /src/osx/ts_osx_keymap.c: -------------------------------------------------------------------------------- 1 | /* 2 | ts_osx_keymap.c 3 | 4 | Copyright 2011 Michel Pollet 5 | 6 | This file is part of touchstream. 7 | 8 | touchstream is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | touchstream is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with touchstream. If not, see . 20 | */ 21 | 22 | 23 | #include 24 | #include "ts_defines.h" 25 | #include "ts_keymap.h" 26 | 27 | /* 28 | * Most of this material is a rehash of synergy's COSXKeyState.cpp 29 | */ 30 | // Note that some virtual keys codes appear more than once. The 31 | // first instance of a virtual key code maps to the KeyID that we 32 | // want to generate for that code. The others are for mapping 33 | // different KeyIDs to a single key code. 34 | enum { 35 | s_shiftVK = 56, 36 | s_controlVK = 59, 37 | s_altVK = 58, 38 | s_superVK = 55, 39 | s_capsLockVK = 57, 40 | s_numLockVK = 71, 41 | s_osxNumLock = 1 << 16, 42 | }; 43 | 44 | static const uint32_t s_controlKeys[255] = { 45 | // cursor keys. if we don't do this we'll may still get these from 46 | // the keyboard resource but they may not correspond to the arrow 47 | // keys. 48 | [123] = kKeyLeft, 49 | [124] = kKeyRight, 50 | [126] = kKeyUp, 51 | [125] = kKeyDown, 52 | [115] = kKeyHome, 53 | [119] = kKeyEnd, 54 | [116] = kKeyPageUp, 55 | [121] = kKeyPageDown, 56 | [114] = kKeyInsert, 57 | 58 | // function keys 59 | [122] = kKeyF1, 60 | [120] = kKeyF2, 61 | [99] = kKeyF3, 62 | [118] = kKeyF4, 63 | [96] = kKeyF5, 64 | [97] = kKeyF6, 65 | [98] = kKeyF7, 66 | [100] = kKeyF8, 67 | [101] = kKeyF9, 68 | [109] = kKeyF10, 69 | [103] = kKeyF11, 70 | [111] = kKeyF12, 71 | [105] = kKeyF13, 72 | [107] = kKeyF14, 73 | [113] = kKeyF15, 74 | [106] = kKeyF16, 75 | 76 | [82] = kKeyKP_0, 77 | [83] = kKeyKP_1, 78 | [84] = kKeyKP_2, 79 | [85] = kKeyKP_3, 80 | [86] = kKeyKP_4, 81 | [87] = kKeyKP_5, 82 | [88] = kKeyKP_6, 83 | [89] = kKeyKP_7, 84 | [91] = kKeyKP_8, 85 | [92] = kKeyKP_9, 86 | [65] = kKeyKP_Decimal, 87 | [81] = kKeyKP_Equal, 88 | [67] = kKeyKP_Multiply, 89 | [69] = kKeyKP_Add, 90 | [75] = kKeyKP_Divide, 91 | [79] = kKeyKP_Subtract, 92 | [76] = kKeyKP_Enter, 93 | 94 | // virtual key 110 is fn+enter and i have no idea what that's supposed 95 | // to map to. also the enter key with numlock on is a modifier but i 96 | // don't know which. 97 | 98 | // modifier keys. OS X doesn't seem to support right handed versions 99 | // of modifier keys so we map them to the left handed versions. 100 | [s_shiftVK] = kKeyShift_L, 101 | [60] = kKeyShift_R, 102 | [s_controlVK] = kKeyControl_L, 103 | [62] = kKeyControl_R, 104 | [s_altVK] = kKeyAlt_L, 105 | [61] = kKeyAlt_R, 106 | 107 | [s_superVK] = kKeyMeta_L, 108 | [54] = kKeyMeta_R, 109 | 110 | // toggle modifiers 111 | [s_numLockVK] = kKeyNumLock, 112 | [s_capsLockVK] = kKeyCapsLock 113 | }; 114 | 115 | uint32_t 116 | unicharToKeyID(UniChar c) 117 | { 118 | switch (c) { 119 | case 3: 120 | return kKeyKP_Enter; 121 | case 8: 122 | return kKeyBackSpace; 123 | case 9: 124 | return kKeyTab; 125 | case 13: 126 | return kKeyReturn; 127 | case 27: 128 | return kKeyEscape; 129 | case 127: 130 | return kKeyDelete; 131 | default: 132 | if (c < 32) 133 | return kKeyNone; 134 | return c; 135 | } 136 | } 137 | 138 | uint32_t keyDownMap[255]; 139 | 140 | uint32_t 141 | mapKeyFromEvent( 142 | uint32_t * maskOut, 143 | CGEventRef event) 144 | { 145 | // get virtual key 146 | UInt32 vkCode = CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode); 147 | 148 | // handle up events 149 | UInt32 eventKind = CGEventGetType(event); 150 | if (eventKind == kCGEventKeyUp) { 151 | // the id isn't used. we just need the same button we used on 152 | // the key press. note that we don't use or reset the dead key 153 | // state; up events should not affect the dead key state. 154 | uint32_t res = keyDownMap[vkCode]; 155 | keyDownMap[vkCode] = 0; 156 | return res ? res : vkCode; 157 | } 158 | 159 | // check for special keys 160 | if (s_controlKeys[vkCode]) { 161 | // printf("%s mapped special key %3d to %4x\n", __func__, (int)vkCode, (int)s_controlKeys[vkCode]); 162 | return keyDownMap[vkCode] = s_controlKeys[vkCode]; 163 | } 164 | 165 | // get keyboard info 166 | 167 | TISInputSourceRef currentKeyboardLayout = TISCopyCurrentKeyboardLayoutInputSource(); 168 | 169 | if (currentKeyboardLayout == NULL) { 170 | printf("currentKeyboardLayout NULL!\n"); 171 | return kKeyNone; 172 | } 173 | 174 | // choose action 175 | UInt16 action; 176 | if (eventKind == kCGEventKeyDown) 177 | action = kUCKeyActionDown; 178 | else if (CGEventGetIntegerValueField(event, kCGKeyboardEventAutorepeat) == 1) 179 | action = kUCKeyActionAutoKey; 180 | else 181 | return 0; 182 | 183 | // translate via uchr resource 184 | CFDataRef ref = (CFDataRef) TISGetInputSourceProperty(currentKeyboardLayout, 185 | kTISPropertyUnicodeKeyLayoutData); 186 | const UCKeyboardLayout* layout = (const UCKeyboardLayout*) CFDataGetBytePtr(ref); 187 | const int layoutValid = (layout != NULL); 188 | 189 | if (layoutValid) { 190 | static UInt32 m_deadKeyState = 0; 191 | UInt32 modifiers = 0; 192 | // translate key 193 | UniCharCount count; 194 | UniChar chars[2]; 195 | // LOG((CLOG_DEBUG2 "modifiers: %08x", modifiers & 0xffu)); 196 | OSStatus status = UCKeyTranslate(layout, 197 | vkCode & 0xffu, action, 198 | (modifiers >> 8) & 0xffu, 199 | LMGetKbdType(), 0, &m_deadKeyState, 200 | sizeof(chars) / sizeof(chars[0]), &count, chars); 201 | 202 | // get the characters 203 | if (status == 0) { 204 | if (count != 0 || m_deadKeyState == 0) { 205 | m_deadKeyState = 0; 206 | return keyDownMap[vkCode] = unicharToKeyID(chars[0]); 207 | } 208 | } 209 | } 210 | 211 | return 0; 212 | } 213 | -------------------------------------------------------------------------------- /src/osx/ts_osx_clipboard.c: -------------------------------------------------------------------------------- 1 | /* 2 | ts_osx_clipboard.c 3 | 4 | Copyright 2011 Michel Pollet 5 | 6 | This file is part of touchstream. 7 | 8 | touchstream is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | touchstream is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with touchstream. If not, see . 20 | */ 21 | 22 | /* 23 | * This file implements copy and paste on OSX 24 | * 25 | * What it does first is use the normal ts_diplay callbacks, and schedule 26 | * an "immediate" timer on the main OSX CFRunLoop to ensure the clipboard 27 | * operations are run on the same thread as the normal even loop. 28 | * 29 | * This is not /strictly/ necessary but it also allows to return quicker to 30 | * the caller (who is probably a ts_mux too) 31 | * 32 | * The clipboard support only handles UTF8 33 | */ 34 | #include 35 | #include "ts_display.h" 36 | #include "ts_master.h" 37 | #include "ts_display_proxy.h" 38 | #include "ts_verbose.h" 39 | 40 | static PasteboardRef clip = 0; 41 | static ts_display_p loop_display; 42 | static ts_clipboard_p loop_clipboard; 43 | static ts_display_p loop_to = NULL; 44 | 45 | static void 46 | osx_driver_getclipboard_loop( 47 | struct ts_display_t *display, 48 | struct ts_display_t *to) 49 | { 50 | if (!clip) 51 | PasteboardCreate(kPasteboardClipboard, &clip); 52 | if (!clip) 53 | return; 54 | 55 | ts_clipboard_clear(&display->clipboard); 56 | 57 | CFDataRef cfdata; 58 | OSStatus err = noErr; 59 | ItemCount nItems; 60 | uint32_t i; 61 | 62 | PasteboardSynchronize(clip); 63 | if ((err = PasteboardGetItemCount(clip, &nItems)) != noErr) { 64 | V1("apple pasteboard GetItemCount failed\n"); 65 | return; 66 | } 67 | 68 | for (i = 1; i <= nItems; ++i) { 69 | PasteboardItemID itemID; 70 | CFArrayRef flavorTypeArray; 71 | CFIndex flavorCount; 72 | 73 | if ((err = PasteboardGetItemIdentifier(clip, i, &itemID)) != noErr) { 74 | V1("can't get pasteboard item identifier\n"); 75 | return; 76 | } 77 | 78 | if ((err = PasteboardCopyItemFlavors(clip, itemID, 79 | &flavorTypeArray)) != noErr) { 80 | V1("Can't copy pasteboard item flavors\n"); 81 | return; 82 | } 83 | 84 | flavorCount = CFArrayGetCount(flavorTypeArray); 85 | CFIndex flavorIndex; 86 | for (flavorIndex = 0; flavorIndex < flavorCount; ++flavorIndex) { 87 | CFStringRef flavorType; 88 | flavorType = (CFStringRef) CFArrayGetValueAtIndex(flavorTypeArray, 89 | flavorIndex); 90 | if (UTTypeConformsTo(flavorType, CFSTR("public.utf8-plain-text"))) { 91 | if ((err = PasteboardCopyItemFlavorData(clip, itemID, 92 | CFSTR("public.utf8-plain-text"), &cfdata)) != noErr) { 93 | V1("apple pasteboard CopyItem failed\n"); 94 | return; 95 | } 96 | CFIndex length = CFDataGetLength(cfdata); 97 | uint8_t * data = malloc(length + 1); 98 | 99 | CFDataGetBytes(cfdata, CFRangeMake(0, length), data); 100 | data[length] = 0; 101 | V1 ("%s DATA %d!! '%s'\n", __func__, (int)length, data); 102 | ts_clipboard_add(&display->clipboard, "text", data, length); 103 | 104 | CFRelease(cfdata); 105 | } 106 | } 107 | CFRelease(flavorTypeArray); 108 | } 109 | if (display->clipboard.flavorCount) 110 | ts_display_setclipboard( 111 | to, 112 | &display->clipboard); 113 | } 114 | 115 | void 116 | osx_driver_setclipboard_loop( 117 | struct ts_display_t *d, 118 | ts_clipboard_p clipboard) 119 | { 120 | if (!clip) 121 | PasteboardCreate(kPasteboardClipboard, &clip); 122 | 123 | if (!clipboard->flavorCount) 124 | return; 125 | 126 | for (int i = 0; i < clipboard->flavorCount; i++) { 127 | if (!strcmp(clipboard->flavor[i].name, "text")) { 128 | V1("%s adding %d bytes of %s\n", __func__, 129 | (int)clipboard->flavor[i].size, clipboard->flavor[i].name); 130 | if (PasteboardClear(clip) != noErr) { 131 | V1("apple pasteboard clear failed"); 132 | return; 133 | } 134 | PasteboardSyncFlags flags = PasteboardSynchronize(clip); 135 | if ((flags & kPasteboardModified) || !(flags & kPasteboardClientIsOwner)) { 136 | V1("apple pasteboard cannot assert ownership"); 137 | return; 138 | } 139 | CFDataRef cfdata = CFDataCreate(kCFAllocatorDefault, 140 | (uint8_t*)clipboard->flavor[i].data, 141 | clipboard->flavor[i].size); 142 | 143 | if (cfdata == nil) { 144 | V1("apple pasteboard cfdatacreate failed"); 145 | return; 146 | } 147 | if (PasteboardPutItemFlavor(clip, (PasteboardItemID) 1, 148 | CFSTR("public.utf8-plain-text"), cfdata, 0) != noErr) { 149 | V1("apple pasteboard putitem failed"); 150 | CFRelease(cfdata); 151 | } 152 | CFRelease(cfdata); 153 | return; 154 | } 155 | } 156 | } 157 | 158 | static void 159 | osx_getclipboard_callback ( 160 | CFRunLoopTimerRef timer, 161 | void *info) 162 | { 163 | //printf("%s was called\n", __func__); 164 | osx_driver_getclipboard_loop(loop_display, loop_to); 165 | } 166 | 167 | static void 168 | osx_setclipboard_callback ( 169 | CFRunLoopTimerRef timer, 170 | void *info) 171 | { 172 | //printf("%s was called\n", __func__); 173 | osx_driver_setclipboard_loop(loop_display, loop_clipboard); 174 | } 175 | 176 | void 177 | osx_driver_getclipboard( 178 | struct ts_display_t *display, 179 | struct ts_display_t *to) 180 | { 181 | //printf("%s called\n", __func__); 182 | loop_display = display; 183 | loop_clipboard = NULL; 184 | loop_to = to; 185 | 186 | CFRunLoopTimerRef timer = CFRunLoopTimerCreate(NULL, 0, 0, 0, 0, osx_getclipboard_callback, NULL); 187 | 188 | CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes); 189 | } 190 | 191 | void 192 | osx_driver_setclipboard( 193 | struct ts_display_t *display, 194 | ts_clipboard_p clipboard) 195 | { 196 | //printf("%s called\n", __func__); 197 | loop_display = display; 198 | loop_clipboard = clipboard; 199 | loop_to = NULL; 200 | 201 | CFRunLoopTimerRef timer = CFRunLoopTimerCreate(NULL, 0, 0, 0, 0, osx_setclipboard_callback, NULL); 202 | 203 | CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes); 204 | } 205 | -------------------------------------------------------------------------------- /src/ts_display.h: -------------------------------------------------------------------------------- 1 | /* 2 | sh_display.h 3 | 4 | Copyright 2011 Michel Pollet 5 | 6 | This file is part of touchstream. 7 | 8 | touchstream is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | touchstream is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with touchstream. If not, see . 20 | */ 21 | 22 | /* 23 | * a "display" is what describes a screen. It is mostly just a rectangle, and a 24 | * 'local' (zero based) mouse position. 25 | * 26 | * The "active" display is the one that gets the events sent to itself, all the 27 | * other ones don't get them. 28 | * 29 | * Associated with the display is a "driver" -- a list of callbacks that are called 30 | * for each of the events that display can handle, not all callbacks are needed 31 | * 32 | * There are currently a few "drivers" 33 | * + OSX "master" one, mostly cares about enter/leave and the clipbaord calls, 34 | * the 'user input' ones aren't needed since it's a master screen 35 | * + xorg "slave" one, this one implements all the callback, and sends the event 36 | * received to the target Xorg connection using XTest etc. 37 | * + A "proxy" one, needed because the OSX one can't call the "xorg" directly, since 38 | * they live on different threads. Instead, the proxy queues the events and 39 | * deliver them to the "slave" driver on the mux's (xorg) thread. 40 | * + A "fake" one, that is used my the mux "data" remote to recover the clipboard 41 | * from a remote client screen and send it back to the server 42 | * 43 | * A display also has a "clipboard", a primitive structure where it can store 44 | * "flavors" of data. See ts_clipboard.[ch] 45 | */ 46 | #ifndef __SH_DISPLAY_H___ 47 | #define __SH_DISPLAY_H___ 48 | 49 | #include 50 | 51 | #include "ts_clipboard.h" 52 | 53 | typedef struct ts_rect_t { 54 | int16_t x, y, w, h; 55 | } ts_rect_t, *ts_rect_p; 56 | 57 | struct ts_display_t; 58 | struct ts_master_t; 59 | 60 | typedef struct ts_display_driver_t { 61 | unsigned int _mutable : 1; // can be free()ed ? 62 | void * refCon; // reference constant, optional, used by callbacks 63 | void (*init)(struct ts_display_t *d); 64 | void (*dispose)(struct ts_display_t *d); 65 | void (*run)(struct ts_display_t *d); 66 | 67 | void (*enter)(struct ts_display_t *d); 68 | void (*leave)(struct ts_display_t *d); 69 | 70 | void (*mouse)(struct ts_display_t *d, int dx, int dy); 71 | void (*button)(struct ts_display_t *d, int b, int down); 72 | void (*key)(struct ts_display_t *d, uint16_t k, int down); 73 | void (*wheel)(struct ts_display_t *d, int wheel, int y, int x); 74 | 75 | void (*getclipboard)(struct ts_display_t *d, struct ts_display_t *to); 76 | void (*setclipboard)(struct ts_display_t *d, ts_clipboard_p clipboard); 77 | } ts_display_driver_t, *ts_display_driver_p; 78 | 79 | 80 | typedef struct ts_display_t { 81 | struct ts_master_t * master; 82 | char * name; 83 | char * param; 84 | ts_display_driver_p driver; 85 | 86 | ts_rect_t bounds; 87 | unsigned int active : 1, moved : 1; 88 | 89 | int mousex, mousey; 90 | 91 | ts_clipboard_t clipboard; 92 | } ts_display_t, *ts_display_p; 93 | 94 | /* 95 | * Initializes display 'd'. Sets it's master to 'master' BUT does NOT 96 | * add it to the master's list just yet. 97 | * 'driver' is a set of (optional) callbacks to call when this display 98 | * becomes active, 99 | * the 'name' is just a handle to allow referal, it is typicaly the 100 | * hostname of the display is on. 101 | * 'param' is a parameter string that is currently used for ts_display_place() 102 | */ 103 | void 104 | ts_display_init( 105 | ts_display_p d, 106 | struct ts_master_t * master, 107 | ts_display_driver_p driver, 108 | char * name, 109 | char * param ); 110 | void 111 | ts_display_dispose( 112 | ts_display_p d ); 113 | 114 | /* 115 | * moves 'which' origin to be 'where' relative to 'main'. 116 | * if 'main' is NULL, the master's main disolay is used. 117 | * 118 | * 'where' can be 'right', 'top', 'left', 'bottom' for now, 119 | * TODO finish extra placement flags 120 | */ 121 | int 122 | ts_display_place( 123 | ts_display_p main, 124 | ts_display_p which, 125 | char * where ); 126 | 127 | /* 128 | * returns a mutable copy of a driver 129 | */ 130 | ts_display_driver_p 131 | ts_display_clone_driver( 132 | ts_display_driver_p driver ); 133 | 134 | /* 135 | * The following calls try to call down to the driver to do the job 136 | */ 137 | void 138 | ts_display_run( 139 | ts_display_p d ); 140 | 141 | void 142 | ts_display_enter( 143 | ts_display_p d); 144 | void 145 | ts_display_leave( 146 | ts_display_p d ); 147 | int 148 | ts_display_movemouse( 149 | ts_display_p d, 150 | int dx, int dy ); 151 | void 152 | ts_display_button( 153 | ts_display_p d, 154 | int button, int down ); 155 | void 156 | ts_display_wheel( 157 | ts_display_p d, 158 | int button, int x, int y ); 159 | void 160 | ts_display_key( 161 | ts_display_p d, 162 | uint16_t key, int down ); 163 | void 164 | ts_display_getclipboard( 165 | ts_display_p d, 166 | ts_display_p to); 167 | void 168 | ts_display_setclipboard( 169 | ts_display_p d, 170 | ts_clipboard_p clipboard ); 171 | 172 | 173 | /* 174 | * Rectangle utilities 175 | */ 176 | 177 | // Return != 0 if x,y is in 'r' 178 | static inline int 179 | ts_ptinrect( 180 | ts_rect_p r, 181 | int x, int y) 182 | { 183 | return x >= r->x && x < r->x + r->w && 184 | y >= r->y && y < r->y + r->h; 185 | } 186 | 187 | // forces x,y to be inside 'r' 188 | static inline void 189 | ts_clippt( 190 | ts_rect_p r, 191 | int *x, int *y) 192 | { 193 | if (*x < r->x) *x = r->x; 194 | if (*x >= r->x + r->w) *x = r->x + r->w - 1; 195 | if (*y < r->y) *y = r->y; 196 | if (*y >= r->y + r->h) *y = r->y + r->h - 1; 197 | } 198 | 199 | // return != 0 if x,h is on an edge of 'r' 200 | static inline int 201 | ts_ptonedge( 202 | ts_rect_p r, 203 | int x, int y) 204 | { 205 | return x == r->x || y == r->y || 206 | x == (r->x + r->w - 1) || (y == r->y + r->h - 1); 207 | } 208 | 209 | // shift x,y by one outside of 'r' if they are on an edge 210 | static inline void 211 | ts_pt_out_edge( 212 | ts_rect_p r, 213 | int *x, int *y) 214 | { 215 | if (*x == r->x) *x -= 1; 216 | if (*y == r->y) *y -= 1; 217 | if (*x == r->x + r->w - 1) *x += 1; 218 | if (*y == r->y + r->h - 1) *y += 1; 219 | } 220 | 221 | #endif /* __SH_DISPLAY_H___ */ 222 | -------------------------------------------------------------------------------- /src/ts_keymap.h: -------------------------------------------------------------------------------- 1 | /* 2 | ts_keymap.h 3 | 4 | Copyright 2011 Michel Pollet 5 | 6 | This file is part of touchstream. 7 | 8 | touchstream is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | touchstream is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY, without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with touchstream. If not, see . 20 | */ 21 | 22 | 23 | #ifndef __TS_KEYMAP_H___ 24 | #define __TS_KEYMAP_H___ 25 | 26 | enum { 27 | KeyModifierShift = 0x0001, 28 | KeyModifierControl = 0x0002, 29 | KeyModifierAlt = 0x0004, 30 | KeyModifierMeta = 0x0008, 31 | KeyModifierSuper = 0x0010, 32 | KeyModifierAltGr = 0x0020, 33 | KeyModifierCapsLock = 0x1000, 34 | KeyModifierNumLock = 0x2000, 35 | KeyModifierScrollLock = 0x4000, 36 | }; 37 | //@} 38 | 39 | //! @name Modifier key bits 40 | //@{ 41 | enum { 42 | kKeyModifierBitNone = 16, 43 | kKeyModifierBitShift = 0, 44 | kKeyModifierBitControl = 1, 45 | kKeyModifierBitAlt = 2, 46 | kKeyModifierBitMeta = 3, 47 | kKeyModifierBitSuper = 4, 48 | kKeyModifierBitAltGr = 5, 49 | kKeyModifierBitCapsLock = 12, 50 | kKeyModifierBitNumLock = 13, 51 | kKeyModifierBitScrollLock = 14, 52 | kKeyModifierNumBits = 16, 53 | }; 54 | //@} 55 | 56 | //! @name Modifier key identifiers 57 | //@{ 58 | enum { 59 | kKeyModifierIDNull = 0, 60 | kKeyModifierIDShift = 1, 61 | kKeyModifierIDControl = 2, 62 | kKeyModifierIDAlt = 3, 63 | kKeyModifierIDMeta = 4, 64 | kKeyModifierIDSuper = 5, 65 | kKeyModifierIDLast = 6, 66 | }; 67 | //@} 68 | 69 | //! @name Key identifiers 70 | //@{ 71 | // all identifiers except kKeyNone and those in 0xE000 to 0xE0FF 72 | // inclusive are equal to the corresponding X11 keysym - 0x1000. 73 | enum { 74 | // no key 75 | kKeyNone = 0x0000, 76 | 77 | // TTY functions 78 | kKeyBackSpace = 0xEF08, /* back space, back char */ 79 | kKeyTab = 0xEF09, 80 | kKeyLinefeed = 0xEF0A, /* Linefeed, LF */ 81 | kKeyClear = 0xEF0B, 82 | kKeyReturn = 0xEF0D, /* Return, enter */ 83 | kKeyPause = 0xEF13, /* Pause, hold */ 84 | kKeyScrollLock = 0xEF14, 85 | kKeySysReq = 0xEF15, 86 | kKeyEscape = 0xEF1B, 87 | kKeyHenkan = 0xEF23, /* Start/Stop Conversion */ 88 | kKeyHangulKana = 0xEF26, /* Hangul, Kana */ 89 | kKeyHiraganaKatakana = 0xEF27, /* Hiragana/Katakana toggle */ 90 | kKeyZenkaku = 0xEF2A, /* Zenkaku/Hankaku */ 91 | kKeyHanjaKanzi = 0xEF2A, /* Hanja, Kanzi */ 92 | kKeyDelete = 0xEFFF, /* Delete, rubout */ 93 | 94 | // cursor control 95 | kKeyHome = 0xEF50, 96 | kKeyLeft = 0xEF51, /* Move left, left arrow */ 97 | kKeyUp = 0xEF52, /* Move up, up arrow */ 98 | kKeyRight = 0xEF53, /* Move right, right arrow */ 99 | kKeyDown = 0xEF54, /* Move down, down arrow */ 100 | kKeyPageUp = 0xEF55, 101 | kKeyPageDown = 0xEF56, 102 | kKeyEnd = 0xEF57, /* EOL */ 103 | kKeyBegin = 0xEF58, /* BOL */ 104 | 105 | // misc functions 106 | kKeySelect = 0xEF60, /* Select, mark */ 107 | kKeyPrint = 0xEF61, 108 | kKeyExecute = 0xEF62, /* Execute, run, do */ 109 | kKeyInsert = 0xEF63, /* Insert, insert here */ 110 | kKeyUndo = 0xEF65, /* Undo, oops */ 111 | kKeyRedo = 0xEF66, /* redo, again */ 112 | kKeyMenu = 0xEF67, 113 | kKeyFind = 0xEF68, /* Find, search */ 114 | kKeyCancel = 0xEF69, /* Cancel, stop, abort, exit */ 115 | kKeyHelp = 0xEF6A, /* Help */ 116 | kKeyBreak = 0xEF6B, 117 | kKeyAltGr = 0xEF7E, /* Character set switch */ 118 | kKeyNumLock = 0xEF7F, 119 | 120 | // keypad 121 | kKeyKP_Space = 0xEF80, /* space */ 122 | kKeyKP_Tab = 0xEF89, 123 | kKeyKP_Enter = 0xEF8D, /* enter */ 124 | kKeyKP_F1 = 0xEF91, /* PF1, KP_A, ... */ 125 | kKeyKP_F2 = 0xEF92, 126 | kKeyKP_F3 = 0xEF93, 127 | kKeyKP_F4 = 0xEF94, 128 | kKeyKP_Home = 0xEF95, 129 | kKeyKP_Left = 0xEF96, 130 | kKeyKP_Up = 0xEF97, 131 | kKeyKP_Right = 0xEF98, 132 | kKeyKP_Down = 0xEF99, 133 | kKeyKP_PageUp = 0xEF9A, 134 | kKeyKP_PageDown = 0xEF9B, 135 | kKeyKP_End = 0xEF9C, 136 | kKeyKP_Begin = 0xEF9D, 137 | kKeyKP_Insert = 0xEF9E, 138 | kKeyKP_Delete = 0xEF9F, 139 | kKeyKP_Equal = 0xEFBD, /* equals */ 140 | kKeyKP_Multiply = 0xEFAA, 141 | kKeyKP_Add = 0xEFAB, 142 | kKeyKP_Separator= 0xEFAC, /* separator, often comma */ 143 | kKeyKP_Subtract = 0xEFAD, 144 | kKeyKP_Decimal = 0xEFAE, 145 | kKeyKP_Divide = 0xEFAF, 146 | kKeyKP_0 = 0xEFB0, 147 | kKeyKP_1 = 0xEFB1, 148 | kKeyKP_2 = 0xEFB2, 149 | kKeyKP_3 = 0xEFB3, 150 | kKeyKP_4 = 0xEFB4, 151 | kKeyKP_5 = 0xEFB5, 152 | kKeyKP_6 = 0xEFB6, 153 | kKeyKP_7 = 0xEFB7, 154 | kKeyKP_8 = 0xEFB8, 155 | kKeyKP_9 = 0xEFB9, 156 | 157 | // function keys 158 | kKeyF1 = 0xEFBE, 159 | kKeyF2 = 0xEFBF, 160 | kKeyF3 = 0xEFC0, 161 | kKeyF4 = 0xEFC1, 162 | kKeyF5 = 0xEFC2, 163 | kKeyF6 = 0xEFC3, 164 | kKeyF7 = 0xEFC4, 165 | kKeyF8 = 0xEFC5, 166 | kKeyF9 = 0xEFC6, 167 | kKeyF10 = 0xEFC7, 168 | kKeyF11 = 0xEFC8, 169 | kKeyF12 = 0xEFC9, 170 | kKeyF13 = 0xEFCA, 171 | kKeyF14 = 0xEFCB, 172 | kKeyF15 = 0xEFCC, 173 | kKeyF16 = 0xEFCD, 174 | kKeyF17 = 0xEFCE, 175 | kKeyF18 = 0xEFCF, 176 | kKeyF19 = 0xEFD0, 177 | kKeyF20 = 0xEFD1, 178 | kKeyF21 = 0xEFD2, 179 | kKeyF22 = 0xEFD3, 180 | kKeyF23 = 0xEFD4, 181 | kKeyF24 = 0xEFD5, 182 | kKeyF25 = 0xEFD6, 183 | kKeyF26 = 0xEFD7, 184 | kKeyF27 = 0xEFD8, 185 | kKeyF28 = 0xEFD9, 186 | kKeyF29 = 0xEFDA, 187 | kKeyF30 = 0xEFDB, 188 | kKeyF31 = 0xEFDC, 189 | kKeyF32 = 0xEFDD, 190 | kKeyF33 = 0xEFDE, 191 | kKeyF34 = 0xEFDF, 192 | kKeyF35 = 0xEFE0, 193 | 194 | // modifiers 195 | kKeyShift_L = 0xEFE1, /* Left shift */ 196 | kKeyShift_R = 0xEFE2, /* Right shift */ 197 | kKeyControl_L = 0xEFE3, /* Left control */ 198 | kKeyControl_R = 0xEFE4, /* Right control */ 199 | kKeyCapsLock = 0xEFE5, /* Caps lock */ 200 | kKeyShiftLock = 0xEFE6, /* Shift lock */ 201 | kKeyMeta_L = 0xEFE7, /* Left meta */ 202 | kKeyMeta_R = 0xEFE8, /* Right meta */ 203 | kKeyAlt_L = 0xEFE9, /* Left alt */ 204 | kKeyAlt_R = 0xEFEA, /* Right alt */ 205 | kKeySuper_L = 0xEFEB, /* Left super */ 206 | kKeySuper_R = 0xEFEC, /* Right super */ 207 | kKeyHyper_L = 0xEFED, /* Left hyper */ 208 | kKeyHyper_R = 0xEFEE, /* Right hyper */ 209 | 210 | // multi-key character composition 211 | kKeyCompose = 0xEF20, 212 | kKeyDeadGrave = 0x0300, 213 | kKeyDeadAcute = 0x0301, 214 | kKeyDeadCircumflex = 0x0302, 215 | kKeyDeadTilde = 0x0303, 216 | kKeyDeadMacron = 0x0304, 217 | kKeyDeadBreve = 0x0306, 218 | kKeyDeadAbovedot = 0x0307, 219 | kKeyDeadDiaeresis = 0x0308, 220 | kKeyDeadAbovering = 0x030a, 221 | kKeyDeadDoubleacute = 0x030b, 222 | kKeyDeadCaron = 0x030c, 223 | kKeyDeadCedilla = 0x0327, 224 | kKeyDeadOgonek = 0x0328, 225 | 226 | // more function and modifier keys 227 | kKeyLeftTab = 0xEE20, 228 | 229 | // update modifiers 230 | kKeySetModifiers = 0xEE06, 231 | kKeyClearModifiers = 0xEE07, 232 | 233 | // group change 234 | kKeyNextGroup = 0xEE08, 235 | kKeyPrevGroup = 0xEE0A, 236 | 237 | // extended keys 238 | kKeyEject = 0xE001, 239 | kKeySleep = 0xE05F, 240 | kKeyWWWBack = 0xE0A6, 241 | kKeyWWWForward = 0xE0A7, 242 | kKeyWWWRefresh = 0xE0A8, 243 | kKeyWWWStop = 0xE0A9, 244 | kKeyWWWSearch = 0xE0AA, 245 | kKeyWWWFavorites = 0xE0AB, 246 | kKeyWWWHome = 0xE0AC, 247 | kKeyAudioMute = 0xE0AD, 248 | kKeyAudioDown = 0xE0AE, 249 | kKeyAudioUp = 0xE0AF, 250 | kKeyAudioNext = 0xE0B0, 251 | kKeyAudioPrev = 0xE0B1, 252 | kKeyAudioStop = 0xE0B2, 253 | kKeyAudioPlay = 0xE0B3, 254 | kKeyAppMail = 0xE0B4, 255 | kKeyAppMedia = 0xE0B5, 256 | kKeyAppUser1 = 0xE0B6, 257 | kKeyAppUser2 = 0xE0B7, 258 | }; 259 | 260 | #endif /* __TS_KEYMAP_H___ */ 261 | -------------------------------------------------------------------------------- /src/ts_display_proxy.c: -------------------------------------------------------------------------------- 1 | /* 2 | ts_display_proxy.c 3 | 4 | Copyright 2011 Michel Pollet 5 | 6 | This file is part of touchstream. 7 | 8 | touchstream is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | touchstream is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with touchstream. If not, see . 20 | */ 21 | 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "ts_display_proxy.h" 28 | #include "ts_mux.h" 29 | 30 | DEFINE_FIFO(ts_display_proxy_event_t, proxy_fifo); 31 | 32 | static int 33 | ts_proxy_driver_flush( 34 | struct ts_remote_t * r) 35 | { 36 | { 37 | uint8_t buf[32]; 38 | while (read(r->socket, buf, sizeof(buf)) == sizeof(buf)) 39 | ; 40 | } 41 | 42 | // printf("%s display %p\n", __func__, r->display); 43 | if (!r->display) 44 | return 0; 45 | 46 | ts_display_p display = r->display; 47 | ts_display_proxy_driver_p d = (ts_display_proxy_driver_p)display->driver; 48 | 49 | while (!proxy_fifo_isempty(&d->fifo)) { 50 | ts_display_proxy_event_t e = proxy_fifo_read(&d->fifo); 51 | switch (e.event) { 52 | case ts_proxy_init: 53 | d->slave->init(display); 54 | break; 55 | case ts_proxy_dispose: 56 | d->slave->dispose(display); 57 | break; 58 | case ts_proxy_enter: 59 | d->slave->enter(display); 60 | break; 61 | case ts_proxy_leave: 62 | d->slave->leave(display); 63 | break; 64 | case ts_proxy_mouse: 65 | d->slave->mouse(display, e.u.mouse.x, e.u.mouse.y); 66 | break; 67 | case ts_proxy_button: 68 | d->slave->button(display, e.u.button, e.down); 69 | break; 70 | case ts_proxy_key: 71 | d->slave->key(display, e.u.key, e.down); 72 | break; 73 | case ts_proxy_wheel: 74 | d->slave->wheel(display, e.u.wheel.wheel, e.u.wheel.y, e.u.wheel.x); 75 | break; 76 | case ts_proxy_getclipboard: 77 | d->slave->getclipboard(display, e.u.display); 78 | break; 79 | case ts_proxy_setclipboard: 80 | d->slave->setclipboard(display, e.u.clipboard); 81 | break; 82 | } 83 | } 84 | return 0; 85 | } 86 | 87 | static void 88 | ts_proxy_driver_init( 89 | ts_display_p d) 90 | { 91 | ts_display_proxy_driver_p p = (ts_display_proxy_driver_p)d->driver; 92 | p->remote.display = d; 93 | if (p->slave && !p->slave->init) 94 | return; 95 | ts_display_proxy_event_t e = { 96 | .event = ts_proxy_init, 97 | }; 98 | proxy_fifo_write(&p->fifo, e); 99 | ts_signal(&p->signal, TS_SIGNAL_END0, 0); 100 | } 101 | 102 | static void 103 | ts_proxy_driver_dispose( 104 | ts_display_p d) 105 | { 106 | ts_display_proxy_driver_p p = (ts_display_proxy_driver_p)d->driver; 107 | if (p->slave && !p->slave->dispose) 108 | return; 109 | ts_display_proxy_event_t e = { 110 | .event = ts_proxy_dispose, 111 | }; 112 | proxy_fifo_write(&p->fifo, e); 113 | ts_signal(&p->signal, TS_SIGNAL_END0, 0); 114 | } 115 | 116 | static void 117 | ts_proxy_driver_enter( 118 | ts_display_p d) 119 | { 120 | ts_display_proxy_driver_p p = (ts_display_proxy_driver_p)d->driver; 121 | if (p->slave && !p->slave->enter) 122 | return; 123 | ts_display_proxy_event_t e = { 124 | .event = ts_proxy_enter, 125 | }; 126 | proxy_fifo_write(&p->fifo, e); 127 | ts_signal(&p->signal, TS_SIGNAL_END0, 0); 128 | } 129 | 130 | static void 131 | ts_proxy_driver_leave( 132 | ts_display_p d) 133 | { 134 | ts_display_proxy_driver_p p = (ts_display_proxy_driver_p)d->driver; 135 | if (p->slave && !p->slave->leave) 136 | return; 137 | ts_display_proxy_event_t e = { 138 | .event = ts_proxy_leave, 139 | }; 140 | proxy_fifo_write(&p->fifo, e); 141 | ts_signal(&p->signal, TS_SIGNAL_END0, 0); 142 | } 143 | 144 | static void 145 | ts_proxy_driver_mouse( 146 | ts_display_p d, 147 | int x, int y) 148 | { 149 | ts_display_proxy_driver_p p = (ts_display_proxy_driver_p)d->driver; 150 | if (p->slave && !p->slave->mouse) 151 | return; 152 | ts_display_proxy_event_t e = { 153 | .event = ts_proxy_mouse, 154 | .u.mouse.x = x, 155 | .u.mouse.y = y, 156 | }; 157 | proxy_fifo_write(&p->fifo, e); 158 | ts_signal(&p->signal, TS_SIGNAL_END0, 0); 159 | } 160 | 161 | static void 162 | ts_proxy_driver_button( 163 | ts_display_p d, 164 | int b, 165 | int down) 166 | { 167 | ts_display_proxy_driver_p p = (ts_display_proxy_driver_p)d->driver; 168 | if (p->slave && !p->slave->button) 169 | return; 170 | ts_display_proxy_event_t e = { 171 | .event = ts_proxy_button, 172 | .u.button = b, 173 | .down = down ? 1 : 0, 174 | }; 175 | proxy_fifo_write(&p->fifo, e); 176 | ts_signal(&p->signal, TS_SIGNAL_END0, 0); 177 | } 178 | 179 | static void 180 | ts_proxy_driver_key( 181 | ts_display_p d, 182 | uint16_t k, 183 | int down) 184 | { 185 | ts_display_proxy_driver_p p = (ts_display_proxy_driver_p)d->driver; 186 | if (p->slave && !p->slave->key) 187 | return; 188 | ts_display_proxy_event_t e = { 189 | .event = ts_proxy_key, 190 | .u.key = k, 191 | .down = down ? 1 : 0, 192 | }; 193 | proxy_fifo_write(&p->fifo, e); 194 | ts_signal(&p->signal, TS_SIGNAL_END0, 0); 195 | } 196 | 197 | static void 198 | ts_proxy_driver_wheel( 199 | ts_display_p d, 200 | int wheel, 201 | int y, int x) 202 | { 203 | ts_display_proxy_driver_p p = (ts_display_proxy_driver_p)d->driver; 204 | if (p->slave && !p->slave->wheel) 205 | return; 206 | ts_display_proxy_event_t e = { 207 | .event = ts_proxy_wheel, 208 | .u.wheel.wheel = wheel, 209 | .u.wheel.y = y, 210 | .u.wheel.x = x, 211 | }; 212 | proxy_fifo_write(&p->fifo, e); 213 | ts_signal(&p->signal, TS_SIGNAL_END0, 0); 214 | } 215 | 216 | static void 217 | ts_proxy_driver_getclipboard( 218 | ts_display_p d, 219 | ts_display_p to) 220 | { 221 | ts_display_proxy_driver_p p = (ts_display_proxy_driver_p)d->driver; 222 | if (p->slave && !p->slave->getclipboard) 223 | return; 224 | ts_display_proxy_event_t e = { 225 | .event = ts_proxy_getclipboard, 226 | .u.display = to, 227 | }; 228 | proxy_fifo_write(&p->fifo, e); 229 | ts_signal(&p->signal, TS_SIGNAL_END0, 0); 230 | } 231 | 232 | static void 233 | ts_proxy_driver_setclipboard( 234 | ts_display_p d, 235 | ts_clipboard_p clipboard) 236 | { 237 | ts_display_proxy_driver_p p = (ts_display_proxy_driver_p)d->driver; 238 | //printf("ts_proxy_driver_setclipboard\n"); 239 | if (p->slave && !p->slave->setclipboard) 240 | return; 241 | ts_display_proxy_event_t e = { 242 | .event = ts_proxy_setclipboard, 243 | .u.clipboard = clipboard, 244 | }; 245 | proxy_fifo_write(&p->fifo, e); 246 | ts_signal(&p->signal, TS_SIGNAL_END0, 0); 247 | } 248 | 249 | static ts_display_driver_t ts_proxy_driver = { 250 | .init = ts_proxy_driver_init, 251 | .dispose = ts_proxy_driver_dispose, 252 | .enter = ts_proxy_driver_enter, 253 | .leave = ts_proxy_driver_leave, 254 | .mouse = ts_proxy_driver_mouse, 255 | .button = ts_proxy_driver_button, 256 | .key = ts_proxy_driver_key, 257 | .wheel = ts_proxy_driver_wheel, 258 | .getclipboard = ts_proxy_driver_getclipboard, 259 | .setclipboard = ts_proxy_driver_setclipboard, 260 | }; 261 | 262 | ts_display_driver_p 263 | ts_display_proxy_driver( 264 | ts_mux_p mux, 265 | ts_display_driver_p driver ) 266 | { 267 | ts_display_proxy_driver_p res = malloc(sizeof(ts_display_proxy_driver_t)); 268 | memset(res, 0, sizeof(*res)); 269 | res->driver = ts_proxy_driver; 270 | 271 | if (driver) { 272 | res->slave = ts_display_clone_driver(driver); 273 | ts_signal_init(&res->signal); 274 | 275 | res->remote.display = NULL; 276 | res->remote.mux = mux; 277 | res->remote.socket = res->signal.fd[TS_SIGNAL_END1]; 278 | res->remote.data_read = ts_proxy_driver_flush; 279 | ts_mux_register(&res->remote); 280 | } 281 | return &res->driver; 282 | } 283 | -------------------------------------------------------------------------------- /src/osx/ts_osx_server.c: -------------------------------------------------------------------------------- 1 | /* 2 | ts_osx_server.c 3 | 4 | Copyright 2011 Michel Pollet 5 | 6 | This file is part of touchstream. 7 | 8 | touchstream is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | touchstream is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with touchstream. If not, see . 20 | */ 21 | 22 | 23 | #include "ts_defines.h" 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #ifndef MAC_OS_X_VERSION_10_5 33 | #define MAC_OS_X_VERSION_10_5 34 | #endif 35 | 36 | #include "ts_keymap.h" 37 | #include "ts_verbose.h" 38 | 39 | typedef struct ts_osx_server_t { 40 | ts_display_t display; 41 | 42 | CFMachPortRef m_eventTapPort; 43 | CFRunLoopSourceRef m_eventTapRLSR; 44 | 45 | double scrollSpeed, scrollSpeedFactor; 46 | 47 | int centerx, centery; 48 | } ts_osx_server_t, *ts_osx_server_p; 49 | 50 | #if __cplusplus 51 | extern "C" { 52 | #endif 53 | typedef int CGSConnectionID; 54 | CGError CGSSetConnectionProperty(CGSConnectionID cid, CGSConnectionID targetCID, CFStringRef key, CFTypeRef value); 55 | int _CGSDefaultConnection(); 56 | #if __cplusplus 57 | } 58 | #endif 59 | 60 | static void 61 | updateScrollSpeed( 62 | ts_osx_server_p d) 63 | { 64 | CFPropertyListRef pref = CFPreferencesCopyValue( 65 | CFSTR("com.apple.scrollwheel.scaling") , 66 | kCFPreferencesAnyApplication, 67 | kCFPreferencesCurrentUser, 68 | kCFPreferencesAnyHost); 69 | if (pref != NULL) { 70 | CFTypeID id = CFGetTypeID(pref); 71 | if (id == CFNumberGetTypeID()) { 72 | CFNumberRef value = (CFNumberRef)(pref); 73 | if (CFNumberGetValue(value, kCFNumberDoubleType, &d->scrollSpeed)) { 74 | if (d->scrollSpeed < 0.0) 75 | d->scrollSpeed = 0.0; 76 | } 77 | } 78 | CFRelease(pref); 79 | } 80 | d->scrollSpeedFactor = pow(10.0, d->scrollSpeed); 81 | V3("%s scroll speed %f factor %f\n", __func__, (float)d->scrollSpeed, (float)d->scrollSpeedFactor); 82 | } 83 | 84 | static SInt32 85 | mapScrollWheelToSynergy( 86 | ts_osx_server_p d, 87 | SInt32 x) 88 | { 89 | // return accelerated scrolling but not exponentially scaled as it is 90 | // on the mac. 91 | double w = (1.0 + d->scrollSpeed) * x / d->scrollSpeedFactor; 92 | return (SInt32)(16.0 * w); 93 | } 94 | 95 | static void 96 | updateScreenShape( 97 | ts_osx_server_p d) 98 | { 99 | 100 | // get info for each display 101 | CGDisplayCount displayCount = 0; 102 | 103 | if (CGGetActiveDisplayList(0, NULL, &displayCount) != CGDisplayNoErr) { 104 | return; 105 | } 106 | 107 | if (displayCount == 0) { 108 | return; 109 | } 110 | 111 | CGDirectDisplayID* displays = malloc( 112 | sizeof(CGDirectDisplayID) * displayCount); 113 | if (displays == NULL) { 114 | return; 115 | } 116 | 117 | if (CGGetActiveDisplayList(displayCount, displays, &displayCount) 118 | != CGDisplayNoErr) { 119 | free(displays); 120 | return; 121 | } 122 | 123 | // get smallest rect enclosing all display rects 124 | CGRect totalBounds = CGRectZero; 125 | for (CGDisplayCount i = 0; i < displayCount; ++i) { 126 | CGRect bounds = CGDisplayBounds(displays[i]); 127 | totalBounds = CGRectUnion(totalBounds, bounds); 128 | } 129 | 130 | int m_x, m_y, m_w, m_h; 131 | // get shape of default screen 132 | m_x = (SInt32) totalBounds.origin.x; 133 | m_y = (SInt32) totalBounds.origin.y; 134 | m_w = (SInt32) totalBounds.size.width; 135 | m_h = (SInt32) totalBounds.size.height; 136 | 137 | // get center of default screen 138 | CGDirectDisplayID main = CGMainDisplayID(); 139 | CGRect rect = CGDisplayBounds(main); 140 | d->centerx = (rect.origin.x + rect.size.width) / 2; 141 | d->centery = (rect.origin.y + rect.size.height) / 2; 142 | 143 | free(displays); 144 | 145 | V2("%s: %d,%d %dx%d on %u %s\n", __func__, m_x, m_y, m_w, m_h, 146 | displayCount, (displayCount == 1) ? "display" : "displays"); 147 | d->display.bounds.x = m_x; 148 | d->display.bounds.y = m_y; 149 | d->display.bounds.w = m_w; 150 | d->display.bounds.h = m_h; 151 | } 152 | 153 | static void 154 | displayReconfigurationCallback( 155 | CGDirectDisplayID displayID, 156 | CGDisplayChangeSummaryFlags flags, 157 | void * inUserData) 158 | { 159 | ts_osx_server_p d = inUserData; 160 | 161 | CGDisplayChangeSummaryFlags mask = kCGDisplayMovedFlag | 162 | kCGDisplaySetModeFlag | kCGDisplayAddFlag | kCGDisplayRemoveFlag | 163 | kCGDisplayEnabledFlag | kCGDisplayDisabledFlag | 164 | kCGDisplayMirrorFlag | kCGDisplayUnMirrorFlag | 165 | kCGDisplayDesktopShapeChangedFlag; 166 | 167 | // LOG((CLOG_DEBUG1 "event: display was reconfigured: %x %x %x", flags, mask, flags & mask)); 168 | 169 | if (!(flags & mask)) { /* Something actually did change */ 170 | 171 | V2("%s: screen unchanged\n", __func__); 172 | return; 173 | //screen->updateScreenShape(displayID, flags); 174 | } 175 | V2("%s: screen changed shape; refreshing dimensions\n", __func__); 176 | 177 | updateScreenShape(d); 178 | } 179 | 180 | uint32_t 181 | mapKeyFromEvent( 182 | uint32_t * maskOut, 183 | CGEventRef event); 184 | 185 | static bool 186 | onKey( 187 | ts_osx_server_p d, 188 | CGEventRef event) 189 | { 190 | CGEventType eventKind = CGEventGetType(event); 191 | 192 | uint32_t key = mapKeyFromEvent(NULL, event); 193 | 194 | int down = eventKind == kCGEventKeyDown; 195 | if (eventKind == kCGEventFlagsChanged) { 196 | CGEventFlags new = CGEventGetFlags(event); 197 | switch (key) { 198 | case kKeyShift_L: 199 | case kKeyShift_R: 200 | down = new & kCGEventFlagMaskShift; 201 | break; 202 | case kKeyControl_L: 203 | case kKeyControl_R: 204 | down = new & kCGEventFlagMaskControl; 205 | break; 206 | case kKeyCapsLock: 207 | down = new & kCGEventFlagMaskAlphaShift; 208 | break; 209 | case kKeyMeta_L: 210 | case kKeyMeta_R: 211 | down = new & kCGEventFlagMaskCommand; 212 | key = kKeyControl_L; 213 | break; 214 | case kKeyAlt_L: 215 | case kKeyAlt_R: 216 | down = new & kCGEventFlagMaskAlternate; 217 | break; 218 | break; 219 | } 220 | } 221 | 222 | V3("%s key %04x (%c) %s\n", __func__, key, 223 | (key >= ' ' && key < 127) ? key : '.', 224 | down ? "down" : "up"); 225 | ts_display_key(ts_master_get_active(d->display.master), key, down); 226 | 227 | return true; 228 | } 229 | 230 | static CGEventRef 231 | handleCGInputEvent( 232 | CGEventTapProxy proxy, 233 | CGEventType type, 234 | CGEventRef event, 235 | void* refcon) 236 | { 237 | ts_osx_server_p d = refcon; 238 | CGPoint pos; 239 | 240 | static const int ts_button[] = {0, 2, 1}; 241 | switch(type) { 242 | case kCGEventLeftMouseDown: 243 | case kCGEventRightMouseDown: 244 | case kCGEventOtherMouseDown: { 245 | int b = CGEventGetIntegerValueField(event, kCGMouseEventButtonNumber); 246 | ts_display_button( 247 | ts_master_get_active(d->display.master), 248 | ts_button[b], 1); 249 | UInt32 modifiers; 250 | MouseTrackingResult res; 251 | Point pt; 252 | CGEventRef event = CGEventCreate(NULL); 253 | CGPoint mouse = CGEventGetLocation(event); 254 | pt.h = (short)mouse.x; 255 | pt.v = (short)mouse.y; 256 | CFRelease(event); 257 | } break; 258 | case kCGEventLeftMouseUp: 259 | case kCGEventRightMouseUp: 260 | case kCGEventOtherMouseUp: { 261 | int b = CGEventGetIntegerValueField(event, kCGMouseEventButtonNumber); 262 | ts_display_button( 263 | ts_master_get_active(d->display.master), 264 | ts_button[b], 0); 265 | } break; 266 | case kCGEventMouseMoved: 267 | case kCGEventLeftMouseDragged: 268 | case kCGEventRightMouseDragged: 269 | case kCGEventOtherMouseDragged: { 270 | pos = CGEventGetLocation(event); 271 | ts_master_mouse_move( 272 | d->display.master, 273 | pos.x - d->display.mousex, 274 | pos.y - d->display.mousey); 275 | 276 | if (!d->display.active) { 277 | pos.x = d->centerx; 278 | pos.y = d->centery; 279 | CGWarpMouseCursorPosition(pos); 280 | d->display.mousex = pos.x; 281 | d->display.mousey = pos.y; 282 | } else { 283 | d->display.master->mousex = pos.x; 284 | d->display.master->mousey = pos.y; 285 | } 286 | 287 | // The system ignores our cursor-centering calls if 288 | // we don't return the event. This should be harmless, 289 | // but might register as slight movement to other apps 290 | // on the system. It hasn't been a problem before, though. 291 | return event; 292 | } break; 293 | case kCGEventScrollWheel: { 294 | float sy = CGEventGetDoubleValueField(event, kCGScrollWheelEventDeltaAxis2); 295 | float sx = CGEventGetDoubleValueField(event, kCGScrollWheelEventDeltaAxis1); 296 | int y = mapScrollWheelToSynergy(d, sy); 297 | int x = mapScrollWheelToSynergy(d, sx); 298 | V3("wheel %f %f -> %3d %3d\n", sy, sx, y, x); 299 | if (x || y) 300 | ts_display_wheel( 301 | ts_master_get_active(d->display.master), 302 | 0, y, x); 303 | } break; 304 | case kCGEventKeyDown: 305 | case kCGEventKeyUp: 306 | case kCGEventFlagsChanged: 307 | onKey(d, event); 308 | break; 309 | case kCGEventTapDisabledByTimeout: 310 | // Re-enable our event-tap 311 | CGEventTapEnable(d->m_eventTapPort, true); 312 | V1("Quartz Event tap was disabled by timeout. Re-enabling.\n"); 313 | break; 314 | case kCGEventTapDisabledByUserInput: 315 | V1("Quartz Event tap was disabled by user input!\n"); 316 | break; 317 | case NX_NULLEVENT: 318 | break; 319 | case NX_SYSDEFINED: 320 | // Unknown, forward it 321 | return event; 322 | break; 323 | case NX_NUMPROCS: 324 | break; 325 | default: 326 | V3("Unknown Quartz Event type: 0x%02x\n", type); 327 | } 328 | return d->display.active ? event : NULL; 329 | } 330 | 331 | static void 332 | driver_enter( 333 | ts_display_p display ) 334 | { 335 | ts_osx_server_p d = (ts_osx_server_p)display; 336 | updateScrollSpeed(d); 337 | 338 | // ts_osx_server_p d = (ts_osx_server_p)display; 339 | CGSetLocalEventsSuppressionInterval(0.0f); 340 | 341 | CGPoint pos; 342 | pos.x = display->mousex; 343 | pos.y = display->mousey; 344 | CGWarpMouseCursorPosition(pos); 345 | 346 | } 347 | 348 | static void 349 | driver_leave( 350 | ts_display_p display) 351 | { 352 | // ts_osx_server_p d = (ts_osx_server_p)display; 353 | /* 354 | * this is needed, otherwise the "delta" coordinates we get 355 | * just don't work properly 356 | */ 357 | CGSetLocalEventsSuppressionInterval(0.000001); 358 | } 359 | 360 | static void 361 | driver_init( 362 | ts_display_p display) 363 | { 364 | ts_osx_server_p d = (ts_osx_server_p)display; 365 | 366 | CGDisplayRegisterReconfigurationCallback( 367 | displayReconfigurationCallback, 368 | d /* refcon */); 369 | 370 | updateScreenShape(d); 371 | d->display.active = 1; 372 | 373 | updateScrollSpeed(d); 374 | d->m_eventTapPort=CGEventTapCreate(kCGHIDEventTap, kCGHIDEventTap, 0, 375 | kCGEventMaskForAllEvents, 376 | handleCGInputEvent, 377 | d /* refcon */); 378 | if (!d->m_eventTapPort) { 379 | printf("Failed to create quartz event tap.\n"); 380 | exit(1); 381 | } 382 | d->m_eventTapRLSR = CFMachPortCreateRunLoopSource( 383 | kCFAllocatorDefault, 384 | d->m_eventTapPort, 385 | 0); 386 | 387 | if (!d->m_eventTapRLSR) { 388 | printf("Failed to create a CFRunLoopSourceRef for the quartz event tap.\n"); 389 | exit(1); 390 | } 391 | CFRunLoopAddSource(CFRunLoopGetMain(), 392 | d->m_eventTapRLSR, kCFRunLoopDefaultMode); 393 | 394 | V2("Event handlers installed\n"); 395 | } 396 | 397 | static void 398 | driver_run( 399 | ts_display_p display) 400 | { 401 | CFRunLoopRun(); 402 | } 403 | 404 | void 405 | osx_driver_getclipboard( 406 | struct ts_display_t *display, 407 | struct ts_display_t *to); 408 | void 409 | osx_driver_setclipboard( 410 | struct ts_display_t *d, 411 | ts_clipboard_p clipboard); 412 | 413 | 414 | static ts_display_driver_t ts_osx_server_driver = { 415 | .init = driver_init, 416 | .run = driver_run, 417 | .enter = driver_enter, 418 | .leave = driver_leave, 419 | .getclipboard = osx_driver_getclipboard, 420 | .setclipboard = osx_driver_setclipboard, 421 | }; 422 | 423 | static ts_display_p 424 | ts_osx_server_main_create( 425 | struct ts_mux_t * mux, 426 | ts_master_p master, 427 | char * param) 428 | { 429 | ts_osx_server_p res = malloc(sizeof(ts_osx_server_t)); 430 | memset(res, 0, sizeof(ts_osx_server_t)); 431 | 432 | char host[64]; 433 | gethostname(host, sizeof(host)); 434 | char * dot = strchr(host, '.'); 435 | if (dot) 436 | *dot = 0; 437 | 438 | ts_display_init(&res->display, master, &ts_osx_server_driver, host, NULL); 439 | ts_master_display_add(master, &res->display); 440 | 441 | return &res->display; 442 | } 443 | 444 | extern ts_platform_create_callback_p ts_platform_create_server; 445 | 446 | static void __osx_init() __attribute__((constructor)); 447 | 448 | static void __osx_init() 449 | { 450 | // printf("%s\n",__func__); 451 | if (!AXAPIEnabled()) { 452 | fprintf(stderr, "%s need \"Enable access for assistive devices\"\n", __func__); 453 | exit(1); 454 | } else { 455 | // printf("%s AXAPIEnabled\n", __func__); 456 | } 457 | ts_platform_create_server = ts_osx_server_main_create; 458 | } 459 | 460 | -------------------------------------------------------------------------------- /src/xorg/ts_xorg_client.c: -------------------------------------------------------------------------------- 1 | /* 2 | ts_xorg_client.c 3 | 4 | Copyright 2011 Michel Pollet 5 | 6 | This file is part of touchstream. 7 | 8 | touchstream is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | touchstream is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with touchstream. If not, see . 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include /* for clipboard */ 38 | 39 | #include "ts_defines.h" 40 | #include "ts_mux.h" 41 | #include "ts_xorg.h" 42 | #include "ts_display_proxy.h" 43 | #include "ts_verbose.h" 44 | 45 | typedef struct ts_xorg_client_t { 46 | ts_display_t display; 47 | ts_mux_p mux; 48 | char * displayname; 49 | Display * dp; 50 | Window root; 51 | Window window; 52 | 53 | ts_remote_t remote; 54 | ts_xorg_krev_t map; 55 | 56 | ts_display_p clipboard_destination; 57 | } ts_xorg_client_t, *ts_xorg_client_p; 58 | 59 | static void 60 | ts_xorg_client_driver_getclipboard_complete( 61 | struct ts_display_t *display, 62 | struct ts_display_t *to); 63 | 64 | #define ERR_BUF_SIZE 1024 65 | int 66 | x11_error_handler( 67 | Display* dpy, 68 | XErrorEvent* ee) 69 | { 70 | char error_msg[ERR_BUF_SIZE] = ""; 71 | char message[ERR_BUF_SIZE] = ""; 72 | char default_string[ERR_BUF_SIZE] = ""; 73 | XGetErrorText(dpy, ee->error_code, error_msg, ERR_BUF_SIZE); 74 | // XGetErrorDatabaseText(dpy, "XDAMAGE", message, "", error_msg, ERR_BUF_SIZE); 75 | fprintf(stderr, "** error received from X server: %s\n%s\n%s\n", message, default_string, 76 | error_msg); 77 | return 0; 78 | } 79 | 80 | static Cursor 81 | createBlankCursor( 82 | ts_xorg_client_p d) 83 | { 84 | // this seems just a bit more complicated than really necessary 85 | 86 | // get the closet cursor size to 1x1 87 | unsigned int w, h; 88 | XQueryBestCursor(d->dp, d->root, 1, 1, &w, &h); 89 | 90 | // make bitmap data for cursor of closet size. since the cursor 91 | // is blank we can use the same bitmap for shape and mask: all 92 | // zeros. 93 | const int size = ((w + 7) >> 3) * h; 94 | char* data = malloc(size); 95 | memset(data, 0, size); 96 | 97 | // make bitmap 98 | Pixmap bitmap = XCreateBitmapFromData(d->dp, d->root, data, w, h); 99 | 100 | // need an arbitrary color for the cursor 101 | XColor color; 102 | color.pixel = 0; 103 | color.red = color.green = color.blue = 0; 104 | color.flags = DoRed | DoGreen | DoBlue; 105 | 106 | // make cursor from bitmap 107 | Cursor cursor = XCreatePixmapCursor(d->dp, bitmap, bitmap, 108 | &color, &color, 0, 0); 109 | 110 | // don't need bitmap or the data anymore 111 | free(data); 112 | XFreePixmap(d->dp, bitmap); 113 | 114 | return cursor; 115 | } 116 | 117 | static Atom XA_CLIPBOARD; 118 | 119 | static int 120 | xorg_client_eventloop( 121 | struct ts_remote_t * r) 122 | { 123 | ts_xorg_client_p d = (ts_xorg_client_p)r->display; 124 | 125 | // printf("%s\n", __func__); 126 | XEvent e; 127 | 128 | while (XPending(d->dp)) { 129 | XNextEvent(d->dp, &e); 130 | switch (e.type) { 131 | case SelectionRequest: { 132 | 133 | V2("%s SelectionRequest out window is %x\n",__func__, (int)d->window); 134 | V3("Requester=%x selection=%d target=%d property=%d\n", 135 | (int)e.xselectionrequest.requestor, 136 | (int)e.xselectionrequest.selection, 137 | (int)e.xselectionrequest.target, 138 | (int)e.xselectionrequest.property); 139 | 140 | V3("target=%s\n", 141 | XGetAtomName(d->dp,e.xselectionrequest.target)); 142 | V3("property=%s\n", 143 | XGetAtomName(d->dp, 144 | e.xselectionrequest.property)); 145 | 146 | int result; 147 | if (e.xselectionrequest.requestor != d->window) { 148 | 149 | Atom type; 150 | // XSelectInput(dpy, w, StructureNotifyMask+ExposureMask); 151 | int format; 152 | unsigned long len, bytes_left; 153 | unsigned char *data = NULL; 154 | 155 | result = XGetWindowProperty(d->dp, d->window, 156 | XA_PRIMARY, 0, 10000000L, 0, 157 | XA_STRING, &type, &format, &len, &bytes_left, &data); 158 | V3("twin primary type=%d format=%d len=%d data=%p\n", 159 | (int) type, format, (int) len, data); 160 | if (result == Success) { 161 | //Try to change the property 162 | //Put the XA_PRIMARY string into the requested property of 163 | //requesting window 164 | 165 | result = XChangeProperty(d->dp, e.xselectionrequest.requestor, 166 | e.xselectionrequest.property, e.xselectionrequest.target, 167 | 8, PropModeReplace, (unsigned char *) data, len); 168 | if (result == BadAlloc || result == BadAtom || result == BadMatch 169 | || result == BadValue || result == BadWindow) { 170 | fprintf(stderr, "XChangeProperty failed %d\n", result); 171 | } 172 | } 173 | } 174 | //free(test); 175 | 176 | XSelectionEvent xev; 177 | //make SelectionNotify event 178 | xev.type = SelectionNotify; 179 | xev.send_event = True; 180 | xev.display = d->dp; 181 | xev.requestor = e.xselectionrequest.requestor; 182 | xev.selection = e.xselectionrequest.selection; 183 | xev.target = e.xselectionrequest.target; 184 | xev.property = e.xselectionrequest.property; 185 | xev.time = e.xselectionrequest.time; 186 | 187 | //Send message to requesting window that operation is done 188 | result = XSendEvent(d->dp, xev.requestor, 0, 0L, 189 | (XEvent *) &xev); 190 | if (result == BadValue || result == BadWindow) 191 | fprintf(stderr, "send SelectionRequest failed\n"); 192 | 193 | } break; 194 | case SelectionNotify: { 195 | V2("%s SelectionNotify %d\n", __func__, 196 | (int)XGetSelectionOwner (d->dp, XA_PRIMARY) ); 197 | 198 | if (d->clipboard_destination) 199 | ts_xorg_client_driver_getclipboard_complete( 200 | &d->display, 201 | d->clipboard_destination); 202 | } break; 203 | default: 204 | V2("%s unknown event %d\n", __func__, e.type); 205 | break; 206 | } 207 | } 208 | 209 | return 0; 210 | } 211 | 212 | static void 213 | ts_xorg_client_driver_init( 214 | ts_display_p display) 215 | { 216 | ts_xorg_client_p d = (ts_xorg_client_p)display; 217 | 218 | XSetErrorHandler(x11_error_handler); 219 | d->dp = XOpenDisplay(d->displayname); 220 | 221 | XA_CLIPBOARD = XInternAtom(d->dp, "CLIPBOARD", 0); 222 | 223 | d->root = DefaultRootWindow(d->dp); 224 | // Ignore any error here, this is "just in case" 225 | //int i = 1; 226 | //setsockopt (ConnectionNumber(d->dp), IPPROTO_TCP, TCP_NODELAY, &i, sizeof (i)); 227 | 228 | V2("%s display %s : %p (%s)\n", __func__, d->displayname, d->dp, d->display.param); 229 | 230 | int screen = DefaultScreen(d->dp); 231 | display->bounds.w = DisplayWidth(d->dp, screen); 232 | display->bounds.h = DisplayHeight(d->dp, screen); 233 | 234 | V2("%s %s screen is %dx%d\n", __func__, 235 | display->name, display->bounds.w, display->bounds.h); 236 | 237 | ts_xorg_keymap_load(&d->map, d->dp); 238 | 239 | XTestGrabControl(d->dp, True); 240 | 241 | XSetWindowAttributes attr; 242 | attr.do_not_propagate_mask = 0; 243 | attr.cursor = createBlankCursor(d); 244 | attr.override_redirect = True; 245 | attr.event_mask = LeaveWindowMask; 246 | 247 | // create and return the window 248 | d->window = XCreateWindow(d->dp, d->root, 0, 0, 1, 1, 0, 0, 249 | InputOnly, CopyFromParent, 250 | CWDontPropagate | CWEventMask | 251 | CWOverrideRedirect | CWCursor, 252 | &attr); 253 | 254 | // select window for property changes 255 | { 256 | XWindowAttributes attr; 257 | XGetWindowAttributes(d->dp, d->window, &attr); 258 | XSelectInput(d->dp, d->window, 259 | attr.your_event_mask | StructureNotifyMask | PropertyChangeMask); 260 | } 261 | CARD16 level; 262 | BOOL state; 263 | DPMSInfo(d->dp, &level, &state); 264 | V3("%s %s is currently %s\n", __func__, display->name, state ? "OFF" : "ON"); 265 | 266 | //DPMSEnable(d->dp); 267 | DPMSDisable(d->dp); 268 | 269 | //DPMSForceLevel(d->dp, /*activate ? DPMSModeStandby :*/ DPMSModeOn); 270 | 271 | d->remote.display = display; 272 | d->remote.mux = d->mux; 273 | d->remote.socket = ConnectionNumber(d->dp); 274 | d->remote.data_read = xorg_client_eventloop; 275 | ts_mux_register(&d->remote); 276 | 277 | if (display->param && display->master) 278 | ts_display_place( 279 | ts_master_get_main(display->master), 280 | display, display->param); 281 | } 282 | 283 | static void 284 | ts_xorg_client_driver_run( 285 | ts_display_p display) 286 | { 287 | while(1) 288 | sleep(1); 289 | } 290 | 291 | static void 292 | ts_xorg_client_driver_mouse( 293 | struct ts_display_t *display, 294 | int dx, int dy) 295 | { 296 | ts_xorg_client_p d = (ts_xorg_client_p)display; 297 | XTestFakeMotionEvent( 298 | d->dp, 0, 299 | display->mousex, display->mousey, CurrentTime); 300 | XFlush(d->dp); 301 | } 302 | 303 | static void 304 | ts_xorg_client_driver_button( 305 | struct ts_display_t *display, 306 | int b, int down) 307 | { 308 | ts_xorg_client_p d = (ts_xorg_client_p)display; 309 | 310 | XTestFakeButtonEvent(d->dp, b + 1, down, CurrentTime); 311 | XFlush(d->dp); 312 | } 313 | 314 | static void 315 | ts_xorg_client_driver_wheel( 316 | struct ts_display_t *display, 317 | int wheel, 318 | int y, int x) 319 | { 320 | ts_xorg_client_p d = (ts_xorg_client_p)display; 321 | 322 | int b = x < 0 ? 5 : 4; 323 | int n = x < 0 ? -x : x; 324 | n /= 32; 325 | if (!n) n++; 326 | 327 | V2("%s button %d count %d\n", __func__, b, n); 328 | for (int i = 0; i < n; i++) { 329 | XTestFakeButtonEvent(d->dp, b, 1, CurrentTime); 330 | XTestFakeButtonEvent(d->dp, b, 0, CurrentTime); 331 | } 332 | XFlush(d->dp); 333 | } 334 | 335 | static void 336 | ts_xorg_client_driver_key( 337 | struct ts_display_t *display, uint16_t k, int down) 338 | { 339 | ts_xorg_client_p d = (ts_xorg_client_p)display; 340 | if (k & 0xff00) 341 | k += 0x1000; 342 | V3("%s key 0x%04x %s ", __func__, k, down ? "down" : "up"); 343 | k = ts_xorg_key_to_button(&d->map, k); 344 | V3(" mapped to button 0x%02x\n", k); 345 | 346 | XTestFakeKeyEvent(d->dp, k, down, CurrentTime); 347 | XFlush(d->dp); 348 | } 349 | 350 | static void 351 | ts_xorg_client_driver_getclipboard( 352 | struct ts_display_t *display, 353 | struct ts_display_t *to) 354 | { 355 | ts_xorg_client_p d = (ts_xorg_client_p)display; 356 | 357 | V3("%s\n", __func__); 358 | Display * dpy = d->dp; 359 | 360 | ts_clipboard_clear(&display->clipboard); 361 | 362 | //XSelectInput(dpy, d->window, StructureNotifyMask | ExposureMask); 363 | 364 | Atom tries[] = { XA_PRIMARY /*, XA_CLIPBOARD*/, 0 }; 365 | 366 | for (int i = 0; tries[i]; i++) { 367 | Window Sown = XGetSelectionOwner (dpy, tries[i]); 368 | // printf ("Selection owner for try %d %i\n", i, (int)Sown); 369 | if (Sown == None) 370 | continue; 371 | if (Sown == d->window) { 372 | V2("%s We already own the selection, bailing\n", __func__); 373 | continue; 374 | } 375 | d->clipboard_destination = to; 376 | XConvertSelection (dpy, tries[i], XA_STRING, XA_PRIMARY, 377 | d->window, CurrentTime); 378 | XFlush (dpy); 379 | break; 380 | } 381 | } 382 | 383 | static void 384 | ts_xorg_client_driver_getclipboard_complete( 385 | struct ts_display_t *display, 386 | struct ts_display_t *to) 387 | { 388 | ts_xorg_client_p d = (ts_xorg_client_p)display; 389 | 390 | V3("%s\n", __func__); 391 | Display * dpy = d->dp; 392 | // Copy from application 393 | // Atom a1, a2; 394 | Atom type; 395 | // XSelectInput(dpy, w, StructureNotifyMask+ExposureMask); 396 | int format, result; 397 | unsigned long len, bytes_left; 398 | unsigned char *data; 399 | 400 | ts_clipboard_clear(&display->clipboard); 401 | 402 | // 403 | // Do not get any data, see how much data is there 404 | // 405 | XGetWindowProperty (dpy, d->window, 406 | XA_PRIMARY, 0, 0, // offset - len 407 | 0, // Delete 0==FALSE 408 | XA_STRING, // flag 409 | &type, // return type 410 | &format, // return format 411 | &len, &bytes_left, //that 412 | &data); 413 | V3("type:%i len:%i format:%i byte_left:%i\n", 414 | (int)type, (int)len, (int)format, (int)bytes_left); 415 | if (bytes_left > 0) { 416 | unsigned long none; 417 | result = XGetWindowProperty (dpy, d->window, 418 | XA_PRIMARY, 0, bytes_left, // offset - len 419 | 0, // Delete 0==FALSE 420 | XA_STRING, // flag 421 | &type, // return type 422 | &format, // return format 423 | &len, &none, //that 424 | &data); 425 | 426 | if (result == Success) { 427 | V3 ("%s DATA %d!! '%s'\n", __func__, (int)bytes_left, data); 428 | ts_clipboard_add(&display->clipboard, "text", data, bytes_left); 429 | } 430 | XFree (data); 431 | } 432 | if (display->clipboard.flavorCount) 433 | ts_display_setclipboard( 434 | to, 435 | &display->clipboard); 436 | } 437 | 438 | 439 | static void 440 | ts_xorg_client_driver_setclipboard( 441 | struct ts_display_t *display, 442 | ts_clipboard_p clipboard) 443 | { 444 | if (!clipboard->flavorCount) 445 | return; 446 | 447 | ts_xorg_client_p d = (ts_xorg_client_p)display; 448 | 449 | for (int i = 0; i < clipboard->flavorCount; i++) { 450 | if (!strcmp(clipboard->flavor[i].name, "text")) { 451 | V3("%s adding %d bytes of %s\n", __func__, 452 | clipboard->flavor[i].size, clipboard->flavor[i].name); 453 | 454 | Atom tries[] = { XA_PRIMARY /*, XA_CLIPBOARD*/ , 0 }; 455 | 456 | for (int i = 0; tries[i]; i++) { 457 | XChangeProperty( 458 | d->dp, d->window, tries[i], XA_STRING, 459 | 8, PropModeReplace, 460 | (uint8_t*)clipboard->flavor[i].data, 461 | clipboard->flavor[i].size); 462 | 463 | //make this window own the clipboard selection 464 | XSetSelectionOwner(d->dp, tries[i], d->window, CurrentTime); 465 | if (d->window != XGetSelectionOwner(d->dp, tries[i])) 466 | fprintf(stderr,"%s Could not set CLIPBOARD selection.\n", __func__); 467 | } 468 | XFlush(d->dp); 469 | 470 | return; 471 | } 472 | } 473 | } 474 | 475 | static void 476 | ts_xorg_client_driver_enter( 477 | ts_display_p display) 478 | { 479 | // ts_xorg_client_p d = (ts_xorg_client_p)display; 480 | ts_xorg_client_driver_mouse(display, 0, 0); 481 | } 482 | 483 | static void 484 | ts_xorg_client_driver_leave( 485 | ts_display_p display) 486 | { 487 | // ts_xorg_client_p d = (ts_xorg_client_p)display; 488 | ts_xorg_client_driver_mouse(display, 0, 0); 489 | } 490 | 491 | 492 | static ts_display_driver_t ts_xorg_client_driver = { 493 | .init = ts_xorg_client_driver_init, 494 | .run = ts_xorg_client_driver_run, 495 | .enter = ts_xorg_client_driver_enter, 496 | .leave = ts_xorg_client_driver_leave, 497 | .mouse = ts_xorg_client_driver_mouse, 498 | .button = ts_xorg_client_driver_button, 499 | .wheel = ts_xorg_client_driver_wheel, 500 | .key = ts_xorg_client_driver_key, 501 | .getclipboard = ts_xorg_client_driver_getclipboard, 502 | .setclipboard = ts_xorg_client_driver_setclipboard, 503 | }; 504 | 505 | static ts_display_p 506 | ts_xorg_client_main_create( 507 | ts_mux_p mux, 508 | ts_master_p master, 509 | char * param) 510 | { 511 | ts_xorg_client_p res = malloc(sizeof(ts_xorg_client_t)); 512 | memset(res, 0, sizeof(ts_xorg_client_t)); 513 | 514 | res->mux = mux; 515 | 516 | char host[64]; 517 | gethostname(host, sizeof(host)); 518 | char * dot = strchr(host, '.'); 519 | if (dot) 520 | *dot = 0; 521 | 522 | res->displayname = getenv("DISPLAY") ? getenv("DISPLAY") : ":0.0"; 523 | 524 | ts_display_init(&res->display, master, &ts_xorg_client_driver, host, param); 525 | ts_master_display_add(master, &res->display); 526 | 527 | return &res->display; 528 | } 529 | 530 | static ts_display_p 531 | ts_xorg_client_create( 532 | ts_mux_p mux, 533 | ts_master_p master, 534 | char * param) 535 | { 536 | ts_xorg_client_p res = malloc(sizeof(ts_xorg_client_t)); 537 | memset(res, 0, sizeof(ts_xorg_client_t)); 538 | 539 | res->mux = mux; 540 | 541 | char name[128]; 542 | strcpy(name, param); 543 | char * where = strchr(name, '='); 544 | if (where) { 545 | *where = 0; 546 | where++; 547 | } 548 | char * col = strchr(name, ':'); 549 | if (col) *col = 0; 550 | 551 | struct hostent *hp = gethostbyname(name); 552 | if (!hp) { 553 | fprintf(stderr, "%s host '%s' doesn't exists\n", __func__, name); 554 | return NULL; 555 | } 556 | char displayname[128]; 557 | sprintf(displayname, "%s:%s", 558 | inet_ntoa(*(struct in_addr *) (hp->h_addr_list[0])), 559 | col ? col+1 : "0.0"); 560 | res->displayname = strdup(displayname); 561 | 562 | if (!isdigit(name[0])) { 563 | col = strchr(name, '.'); 564 | if (col) * col = 0; 565 | } 566 | V2("%s name '%s' param '%s' display '%s'\n", __func__, name, param, displayname); 567 | 568 | int doproxy = 1; 569 | ts_display_driver_p driver = &ts_xorg_client_driver; 570 | if (doproxy) { 571 | driver = ts_display_proxy_driver(mux, &ts_xorg_client_driver); 572 | } else { 573 | // that will crash anyway, due to thread issues 574 | ts_xorg_client_driver.getclipboard = NULL; 575 | ts_xorg_client_driver.setclipboard = NULL; 576 | } 577 | ts_display_init(&res->display, master, driver, name, NULL); 578 | 579 | ts_master_display_add(master, &res->display); 580 | 581 | return &res->display; 582 | } 583 | 584 | extern ts_platform_create_callback_p ts_platform_create_client; 585 | extern ts_platform_create_callback_p ts_xorg_create_client; 586 | 587 | static void __xorg_init(void) __attribute__((constructor)); 588 | 589 | static void __xorg_init(void) 590 | { 591 | // printf("%s\n",__func__); 592 | ts_platform_create_client = ts_xorg_client_main_create; 593 | ts_xorg_create_client = ts_xorg_client_create; 594 | } 595 | 596 | -------------------------------------------------------------------------------- /src/ts_mux.c: -------------------------------------------------------------------------------- 1 | /* 2 | ts_mux.c 3 | 4 | Copyright 2011 Michel Pollet 5 | 6 | This file is part of touchstream. 7 | 8 | touchstream is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | touchstream is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with touchstream. If not, see . 20 | */ 21 | 22 | /* 23 | * the packet format is trivial on the link, it's 24 | * a zero terminated string; the 'zero' is also a packet delimiter, 25 | * and therefore can't exist in the payload. 26 | * There is no check for now if for example the clipboard contains zero, 27 | * theoricaly, it's UTF8, so there shouldn't. 28 | * 29 | * A example packet is as follow: 30 | * Svx1w1920h1200nyelp 31 | * Means 32 | * 'S' is packet type, in this case a Server screen notification 33 | * 'vx1' is version hex '1' -- any suite of hex char is read 34 | * 'w1920' sets the width to 1920 35 | * 'h1200' sets the height to 1200 36 | * nyelp sets the name to 'yelp' -- ends at the end of the packet, or ':' 37 | */ 38 | 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | 53 | #include "ts_mux.h" 54 | #include "ts_display_proxy.h" 55 | #include "ts_verbose.h" 56 | 57 | #define TS_MUX_VERSION 0x0001 58 | 59 | DEFINE_FIFO(ts_display_proxy_event_t, proxy_fifo); 60 | 61 | #define _MAX(a, b) ((a) > (b) ? (a) : (b)) 62 | 63 | /* 64 | * Mux/demux thread 65 | * 66 | * This thread waits for events on it's signal socket or on any of the 67 | * 'remote' sockets, and then calls their callbacks to dispatch the event. 68 | * 69 | * Note that the mux thread is used with network socket as expected, but is 70 | * also used for xorg "Connection" and ts_signal socketpair(), it makes sure 71 | * that all the code runs on a single thread, and prevents the need for 72 | * mutexes and other garbage. 73 | */ 74 | static void * 75 | _sn_mux_thread( 76 | void * ignore) 77 | { 78 | ts_mux_p mux = (ts_mux_p)ignore; 79 | 80 | fd_set readSet, writeSet; 81 | while (1) { 82 | int max = -1; 83 | FD_ZERO(&readSet); 84 | FD_ZERO(&writeSet); 85 | 86 | FD_SET(mux->signal.fd[TS_SIGNAL_END1], &readSet); 87 | max = _MAX(max, mux->signal.fd[TS_SIGNAL_END1]); 88 | 89 | /* 90 | * Mark all the remotes ready to read, also check to see if 91 | * they have outgoing data, if so, also check for an event 92 | * to make sure socket is ready to send before sending out 93 | */ 94 | for (int i = 0; i < 32; i++) { 95 | if ((mux->dp_usage & (1U << i))) { 96 | ts_remote_p fun = mux->dp[i]; 97 | if (fun->socket <= 0 && fun->start(fun)) 98 | continue; 99 | 100 | if (!fun->can_read || fun->can_read(fun)) { 101 | FD_SET(fun->socket, &readSet); 102 | max = _MAX(max, fun->socket); 103 | } 104 | 105 | if (fun->can_write && fun->can_write(fun)) { 106 | FD_SET(fun->socket, &writeSet); 107 | max = _MAX(max, fun->socket); 108 | } 109 | } 110 | } 111 | 112 | /* 113 | * Wait for an event on any of the sockets 114 | */ 115 | struct timeval timo = { .tv_sec = 1, .tv_usec = 0 }; 116 | /*int ret = */ 117 | select(max + 1, &readSet, &writeSet, NULL, &timo); 118 | 119 | // have we been signaled ? 120 | if (FD_ISSET(mux->signal.fd[TS_SIGNAL_END1], &readSet)) { 121 | // flush socket 122 | ts_signal_flush(&mux->signal, TS_SIGNAL_END1); 123 | // printf("%s: signaled\n", __FUNCTION__); 124 | } 125 | for (int i = 0; i < 32; i++) 126 | if ((mux->dp_usage & (1U << i))) { 127 | ts_remote_p fun = mux->dp[i]; 128 | if (fun->socket <= 0) /* not yet connected anyway */ 129 | continue; 130 | int rd = FD_ISSET(fun->socket, &readSet); 131 | int wr = FD_ISSET(fun->socket, &writeSet); 132 | if (rd || wr) 133 | V3("slot %d %p socket %d rd %d wr %d\n", 134 | i, fun, fun->socket, rd, wr); 135 | if (rd && fun->data_read) 136 | fun->data_read(fun); 137 | if (wr && fun->data_write) 138 | fun->data_write(fun); 139 | } 140 | } 141 | return NULL; 142 | } 143 | 144 | int 145 | ts_mux_start( 146 | ts_mux_p mux, 147 | ts_master_p master ) 148 | { 149 | if (mux->thread) 150 | return 0; 151 | mux->master = master; 152 | /* 153 | * Create the socket pair for signaling the mux thread 154 | */ 155 | ts_signal_init(&mux->signal); 156 | /* 157 | * Create the thread 158 | */ 159 | if (pthread_create(&mux->thread, NULL, 160 | _sn_mux_thread, mux)) { 161 | perror("_sn_server_mux_create pthread_create"); 162 | mux->thread = 0; 163 | return -1; 164 | } 165 | return 0; 166 | } 167 | 168 | //! Signal the thread side from the client side 169 | void 170 | ts_mux_signal( 171 | ts_mux_p mux, 172 | uint8_t what ) 173 | { 174 | ts_signal(&mux->signal, TS_SIGNAL_END0, 0); 175 | } 176 | 177 | /* 178 | * These are not thread safe, fortunately, they should only happend 179 | * before the thread is started OR in the mux's thread context. 180 | */ 181 | int 182 | ts_mux_register( 183 | ts_remote_p r) 184 | { 185 | for (int i = 0; i < 32; i++) 186 | if (!(r->mux->dp_usage & (1U << i))) { 187 | r->mux->dp[i] = r; 188 | r->mux->dp_usage |= 1 << i; 189 | ts_mux_signal(r->mux, 0); 190 | return 0; 191 | } 192 | return -1; 193 | } 194 | 195 | int 196 | ts_mux_unregister( 197 | ts_remote_p r) 198 | { 199 | for (int i = 0; i < 32; i++) 200 | if ((r->mux->dp_usage & (1U << i)) && r->mux->dp[i] == r) { 201 | r->mux->dp_usage &= ~(1 << i); 202 | r->mux->dp[i] = NULL; 203 | // ts_mux_signal(r->mux, 0); 204 | return 0; 205 | } 206 | return -1; 207 | } 208 | 209 | /* 210 | * Create an outgoing socket, and attempts to connect to it asynchronously 211 | */ 212 | static int 213 | connect_start( 214 | struct ts_remote_t * r) 215 | { 216 | if (r->timeout && time(NULL) - r->timeout < 5) 217 | return -1; 218 | 219 | int skt = socket(AF_INET, SOCK_STREAM, 0); 220 | if (skt < 0) 221 | return -1; 222 | size_t addrLen = sizeof(r->addr); 223 | 224 | r->state = skt_state_Connect; 225 | int i = 1; 226 | // we can ignore error here, on UNIX sockets 227 | setsockopt (skt, IPPROTO_TCP, TCP_NODELAY, &i, sizeof (i)); 228 | 229 | r->timeout = 0; 230 | r->socket = skt; 231 | { // make it nonblocking 232 | int flags = fcntl(r->socket, F_GETFL, 0); 233 | fcntl(r->socket, F_SETFL, flags | O_NONBLOCK); 234 | } 235 | 236 | if (connect(skt, (struct sockaddr*)&r->addr, addrLen) < 0) { 237 | if (errno != EINPROGRESS) { 238 | perror("connect_start"); 239 | close(skt); 240 | return -1; 241 | } 242 | V1("Connection in progress (%s)\n", __func__); 243 | } 244 | 245 | return 0; 246 | } 247 | 248 | /* 249 | * if a remote socket fails, delete it, and set ourself up 250 | * ready to re-attempt connection in a few seconds. 251 | */ 252 | static int 253 | connect_restart( 254 | struct ts_remote_t * r) 255 | { 256 | V1("Outgoing connection retrying (%s)\n", __func__); 257 | r->timeout = time(NULL); 258 | /* 259 | * Note, we do NOT delete the r->display here, as outgoing socket's hold 260 | * the 'main' display there, so we just close the socket and try to 261 | * reconnect to the server instead. 262 | */ 263 | close(r->socket); 264 | r->socket = -1; 265 | return -1; 266 | } 267 | 268 | /* 269 | * This is called when the socket has been truly established. 270 | * Theoricaly, we could use the existing system to buffer & send 271 | * our message, but since we /just started/ we can assume there's 272 | * a few bytes free in the socket's buffer for a write() to work! 273 | */ 274 | static int 275 | connect_established( 276 | struct ts_remote_t * r) 277 | { 278 | V1("Outgoing connection established (%s)\n", __func__); 279 | ts_display_p d = r->display; 280 | char msg[32]; 281 | sprintf(msg, "Cvx%xw%dh%dn%s:p%s:", TS_MUX_VERSION, 282 | d->bounds.w, d->bounds.h, d->name, 283 | d->param ? d->param : ""); 284 | if (write(r->socket, msg, strlen(msg)+1)) 285 | ; 286 | return 0; 287 | } 288 | 289 | /* 290 | * If the outgoing socket is not established, do not 291 | * try to register for read events, it won't work 292 | */ 293 | static int 294 | connect_can_read( 295 | struct ts_remote_t * r) 296 | { 297 | if (r->state == skt_state_Connect) { 298 | return 0; 299 | } 300 | return 1; 301 | } 302 | 303 | /* 304 | * Check to see whether we want to write anything. It could be 305 | * + That we are still establishing the connection, therefore we 306 | * need to register for this event as per select() requirement 307 | * + We have an outgoing buffer that still has some data not sent 308 | * from the last time we tried to send it 309 | * + There are new events in the FIFO to convert into packets 310 | */ 311 | static int 312 | connect_can_write( 313 | struct ts_remote_t * r) 314 | { 315 | if (r->state == skt_state_Connect) { 316 | return 1; 317 | } 318 | if (r->out_len) 319 | return 1; 320 | // check fifo... 321 | if (!r->proxy) 322 | return 0; 323 | 324 | return !proxy_fifo_isempty(&r->proxy->fifo); 325 | } 326 | 327 | /* 328 | * This is called when an incoming socket has been established (from the listen one) 329 | * It means we are a 'server' and therefore we send a 'server' packet quickly 330 | * with the geometry of the screen 331 | */ 332 | static int 333 | data_start( 334 | struct ts_remote_t * r) 335 | { 336 | r->socket = r->accept_socket; 337 | V2("%s Incoming connection socket %d\n", __func__, r->socket); 338 | ts_display_p d = ts_master_get_main(r->mux->master); 339 | char msg[32]; 340 | sprintf(msg, "Svx%xw%dh%dn%s", TS_MUX_VERSION, d->bounds.w, d->bounds.h, d->name); 341 | if (write(r->socket, msg, strlen(msg)+1)) 342 | ; 343 | return 0; 344 | } 345 | 346 | /* 347 | * Incoming socket has closed down, we need to tear down any proxy display 348 | * we had, clean the buffers, unregister ourselves from the mux and die. 349 | */ 350 | static int 351 | data_restart( 352 | struct ts_remote_t * r) 353 | { 354 | V1("Incoming connection to %s terminated (%s)\n", 355 | r->display ? r->display->name : "(unknown)",__func__); 356 | if (r->display) { 357 | ts_master_display_remove(r->display->master, r->display); 358 | r->display = NULL; 359 | } 360 | ts_mux_unregister(r); 361 | if (r->in) 362 | free(r->in); 363 | r->in = NULL; 364 | r->in_size = r->in_len = 0; 365 | if (r->out) 366 | free(r->out); 367 | r->out = NULL; 368 | r->out_size = r->out_len = 0; 369 | if (r->dispose) 370 | r->dispose(r); 371 | else { 372 | close(r->socket); 373 | free(r); 374 | } 375 | return -1; 376 | } 377 | 378 | 379 | /* 380 | * Check to see wether we want to write anything. It could be 381 | * + We have an outgoing buffer that still has some data not sent 382 | * from the last time we tried to send it 383 | * + There are new events in the FIFO to convert into packets 384 | */ 385 | static int 386 | data_can_write( 387 | struct ts_remote_t * r) 388 | { 389 | if (r->out_len) 390 | return 1; 391 | if (!r->proxy) 392 | return 0; 393 | 394 | return !proxy_fifo_isempty(&r->proxy->fifo); 395 | } 396 | 397 | /* INTERNAL PACKET DECODING UTILITY 398 | * extract one numerical parameter from the string, advance the pointer. 399 | * format is either 'xXXXXXX' hex or 'DDDD' 400 | */ 401 | static int 402 | data_get_integer( 403 | uint8_t ** o) 404 | { 405 | int hex = 0; 406 | int minus = 1; 407 | uint8_t *p = *o; 408 | 409 | if (*p == 'x') { 410 | hex = 1; p++; 411 | } else if (*p == '-') { 412 | minus = -1; p++; 413 | } 414 | int res = 0; 415 | if (hex) { 416 | while (isxdigit(*p)) { 417 | char c = tolower(*p); 418 | if (isdigit(c)) 419 | res = (res << 4) | (c - '0'); 420 | else 421 | res = (res << 4) | (c - 'a' + 10); 422 | p++; 423 | } 424 | } else { 425 | while (isdigit(*p)) { 426 | res = (res * 10) + (*p - '0'); 427 | p++; 428 | } 429 | } 430 | *o = p; 431 | return res * minus; 432 | } 433 | 434 | /* INTERNAL PACKET DECODING UTILITY 435 | * Reads a string, terminated with zero or a specified 436 | */ 437 | static char * 438 | data_get_string( 439 | uint8_t **o, 440 | char term ) 441 | { 442 | uint8_t * p = *o; 443 | while (*p && *p != term) 444 | p++; 445 | if (*p) { 446 | *p = 0; p++; 447 | } 448 | uint8_t * res = *o; 449 | *o = p; 450 | return (char*)res; 451 | } 452 | 453 | 454 | /* INTERNAL PACKET UTILITY 455 | * Attemps to write as much as possible from our buffer to the socket. 456 | * Then move the remaining at the start of the buffer until it's empty 457 | * return the bytes remaining 458 | */ 459 | static int 460 | data_event_write_flush( 461 | struct ts_remote_t * r) 462 | { 463 | if (!r->out_len) 464 | return 0; 465 | 466 | ssize_t ss = write(r->socket, r->out, r->out_len); 467 | if (ss < 0) 468 | return -1; 469 | if (ss == r->out_len) 470 | r->out_len = 0; 471 | else { 472 | memmove(r->out, r->out + ss, r->out_len - ss); 473 | r->out_len -= ss; 474 | } 475 | 476 | return r->out_len; 477 | } 478 | 479 | /* INTERNAL PACKET UTILITY 480 | * Tries to make sure there's at least 'size' bytes in the buffer, and return 481 | * a pointer to the place we allocated. 482 | */ 483 | static uint8_t * 484 | data_event_write_alloc( 485 | struct ts_remote_t * r, 486 | int size ) 487 | { 488 | if (r->out_len + size >= r->out_size) { 489 | int news = (r->out_len + size + 128) & ~127; 490 | V3("%s buffer reallocated to %d\n", __func__, (int)news); 491 | r->out = realloc(r->out, news); 492 | r->out_size = news; 493 | } 494 | *(r->out + r->out_len) = 0; 495 | return r->out + r->out_len; 496 | } 497 | 498 | static uint8_t * 499 | data_event_write_commit( 500 | struct ts_remote_t * r, 501 | uint8_t * buf ) 502 | { 503 | if (!buf || !*buf) 504 | return NULL; 505 | int l = strlen((char*)buf); 506 | r->out_len += l + 1; 507 | return NULL; 508 | } 509 | 510 | /* 511 | * This look for text in a clipboard, and generate packets to 512 | * + clear remote clipboard named 'name' 513 | * + set the text flavors 514 | * + set the clipboard once it is fully sent 515 | */ 516 | static void 517 | data_event_write_clipboard( 518 | struct ts_remote_t * r, 519 | ts_clipboard_p clipboard, 520 | char * name) 521 | { 522 | uint8_t * buf = data_event_write_alloc(r, 16); 523 | sprintf((char*)buf, "cn%s", name); 524 | buf = data_event_write_commit(r, buf); 525 | for (int i = 0; i < clipboard->flavorCount; i++) 526 | if (!strncmp(clipboard->flavor[i].name, "text", 4)) { 527 | buf = data_event_write_alloc(r, clipboard->flavor[i].size + 32); 528 | sprintf((char*)buf, "fn%s:F%s:D%s", name, 529 | clipboard->flavor[i].name, 530 | clipboard->flavor[i].data); 531 | buf = data_event_write_commit(r, buf); 532 | } 533 | buf = data_event_write_alloc(r, 16); 534 | sprintf((char*)buf, "sn%s", name); 535 | buf = data_event_write_commit(r, buf); 536 | } 537 | 538 | /* 539 | * Pools the display event fifo, takes the events from there, and packetize 540 | * them into an output buffer, they are then sent as fast as the socket will 541 | * allow. 542 | */ 543 | static int 544 | data_event_write( 545 | struct ts_remote_t * r) 546 | { 547 | /* 548 | * During an outgoing connection to a server, we monitor the socket 549 | * state for a "write event", this is the select() convention 550 | * Once a write event has been received, check for any error, and 551 | * if all is well transform into a normal "data" one... 552 | */ 553 | if (r->state == skt_state_Connect) { 554 | int e = 1; 555 | socklen_t s = sizeof(e); 556 | // we can ignore error here, on UNIX sockets 557 | getsockopt (r->socket, SOL_SOCKET, SO_ERROR, &e, &s); 558 | // printf("data_event_write connection finished error %d\n", e ); 559 | if (e) { 560 | errno = e; 561 | perror("connect failed: "); 562 | connect_restart(r); 563 | return 0; 564 | } 565 | r->state = skt_state_Data; 566 | V2("%s connection established\n", __func__); 567 | connect_established(r); 568 | } 569 | /* 570 | * if there is a buffer with stuff in already, send it off 571 | */ 572 | if (data_event_write_flush(r)) 573 | return 0; 574 | if (!r->proxy) 575 | return 0; 576 | 577 | /* 578 | * Empty the fifo, packetize anything we have 579 | */ 580 | while (!proxy_fifo_isempty(&r->proxy->fifo)) { 581 | ts_display_proxy_event_t e = proxy_fifo_read(&r->proxy->fifo); 582 | uint8_t * buf = NULL; 583 | switch (e.event) { 584 | case ts_proxy_init: 585 | // d->slave->init(display); 586 | break; 587 | case ts_proxy_dispose: 588 | // d->slave->dispose(display); 589 | break; 590 | case ts_proxy_enter: 591 | // d->slave->enter(display, e.u.display, e.flags); 592 | buf = data_event_write_alloc(r, 32); 593 | sprintf((char*)buf, "ex%dy%d", 594 | r->display->mousex, r->display->mousey); 595 | V3("%s: %s\n", __func__, (char*)buf); 596 | break; 597 | case ts_proxy_leave: 598 | buf = data_event_write_alloc(r, 8); 599 | sprintf((char*)buf, "l"); 600 | break; 601 | case ts_proxy_mouse: 602 | buf = data_event_write_alloc(r, 32); 603 | sprintf((char*)buf, "mx%dy%d", 604 | (int)e.u.mouse.x, (int)e.u.mouse.y); 605 | break; 606 | case ts_proxy_button: 607 | buf = data_event_write_alloc(r, 16); 608 | sprintf((char*)buf, "bb%dd%d", 609 | (int)e.u.button, (int)e.down); 610 | break; 611 | case ts_proxy_key: 612 | buf = data_event_write_alloc(r, 16); 613 | sprintf((char*)buf, "kd%dkx%04x", 614 | (int)e.down, e.u.key); 615 | break; 616 | case ts_proxy_wheel: 617 | buf = data_event_write_alloc(r, 16); 618 | sprintf((char*)buf, "wb%dx%dy%d", 619 | (int)e.u.wheel.wheel, 620 | (int)e.u.wheel.x,(int) e.u.wheel.y); 621 | break; 622 | case ts_proxy_getclipboard: 623 | buf = data_event_write_alloc(r, 8); 624 | sprintf((char*)buf, "gn%s", ts_master_get_main(r->mux->master)->name); 625 | break; 626 | case ts_proxy_setclipboard: { 627 | if (!e.u.clipboard || !e.u.clipboard->flavorCount) 628 | break; 629 | data_event_write_clipboard(r, 630 | e.u.clipboard, ts_master_get_main(r->mux->master)->name); 631 | } break; 632 | } 633 | data_event_write_commit(r, buf); 634 | } 635 | /* 636 | * we must have generated packets there, so try to send them now too, instead 637 | * of waiting for another select() event. 638 | */ 639 | data_event_write_flush(r); 640 | return 0; 641 | } 642 | 643 | /* 644 | * This is called when our remote master "screen" wants the clipboard; 645 | * so we do just that, we serialize it, and we send it off 646 | */ 647 | static void ts_mux_remote_setclipboard(struct ts_display_t *d, ts_clipboard_p clipboard) 648 | { 649 | ts_remote_p r = d->driver->refCon; 650 | V3("%s\n", __func__); 651 | 652 | data_event_write_clipboard(r, clipboard, d->name); 653 | ts_mux_signal(r->mux, 0); 654 | } 655 | 656 | static ts_display_driver_t ts_mux_driver_remote = { 657 | .setclipboard = ts_mux_remote_setclipboard, 658 | }; 659 | 660 | /* 661 | * Receive a fully formed packet -- refer to the top of the file for a 662 | * bit more about the packet format. 663 | * One thing of note is that the parameter 'names' is predefined, and the 664 | * parser try to decode as many as it can before looking at the data type. 665 | * Since many packets share parameter names/type, it makes the code a 666 | * lot simpler. 667 | */ 668 | static void 669 | data_process_packet( 670 | struct ts_remote_t * r, 671 | uint8_t * pkt, 672 | size_t len ) 673 | { 674 | uint8_t * p = pkt; 675 | char kind = *p++; 676 | 677 | int v = 0 , w = 0, h = 0; 678 | int x = 0, y = 0; 679 | int b = 0, d = 0; 680 | uint16_t k = 0; 681 | char * param = NULL; 682 | char * name = NULL; 683 | char * flavor = NULL; 684 | char * data = NULL; 685 | 686 | // printf("packet '%s'\n", pkt); 687 | /* 688 | * Try to decode all the parameters we find 689 | */ 690 | int ok = 1; 691 | while (*p && ok) { 692 | switch (*p) { 693 | case 'v': p++; v = data_get_integer(&p); break; // version 694 | case 'w': p++; w = data_get_integer(&p); break; // width 695 | case 'h': p++; h = data_get_integer(&p); break; // height 696 | case 'p': p++; param = data_get_string(&p, ':'); break; // param 697 | case 'n': p++; name = data_get_string(&p, ':'); break; // name 698 | case 'F': p++; flavor = data_get_string(&p, ':'); break; // flavor string 699 | case 'D': p++; data = data_get_string(&p, 0); break; // data, always zero termed 700 | case 'x': p++; x = data_get_integer(&p); break; 701 | case 'y': p++; y = data_get_integer(&p); break; 702 | case 'b': p++; b = data_get_integer(&p); break; // button 703 | case 'd': p++; d = data_get_integer(&p); break; // down/up 704 | case 'k': p++; k = data_get_integer(&p); break; // key (unsigned) 705 | default: ok = 0; 706 | } 707 | } 708 | 709 | switch (kind) { 710 | case 'C': // client screen 711 | case 'S': { // server screen 712 | V3("packet %s %s version %04x screen %4dx%4d param '%s'\n", 713 | kind == 's' ? "server" : "client", name, v, w, h, param ? (char*)param : "(none)"); 714 | if (!(kind && w && h)) { 715 | V1("%s invalid '%c' packet anyway\n", __func__, kind); 716 | break; 717 | } 718 | 719 | ts_display_driver_p driver = NULL; 720 | if (kind == 'C') { 721 | // we're the server, let's setup a proxy screen to start making packets 722 | V1("Setting up new client screen '%s' (%s) \n", name, __func__); 723 | 724 | driver = ts_display_proxy_driver(r->mux, NULL); 725 | r->proxy = (ts_display_proxy_driver_p)driver; 726 | r->proxy->signal.fd[TS_SIGNAL_END0] = r->mux->signal.fd[TS_SIGNAL_END0]; 727 | } else { 728 | V1("Setting server screen '%s' (%s) \n", name, __func__); 729 | driver = ts_display_clone_driver(&ts_mux_driver_remote); 730 | driver->refCon = r; 731 | param = r->display->param; 732 | } 733 | ts_display_p new_display = malloc(sizeof(ts_display_t)); 734 | ts_display_init(new_display, r->mux->master, driver, name, param); 735 | new_display->bounds.w = w; 736 | new_display->bounds.h = h; 737 | ts_master_display_add(r->mux->master, new_display); 738 | 739 | if (kind == 'C') { 740 | r->display = new_display; 741 | ts_display_place( 742 | ts_master_get_main(r->mux->master), 743 | new_display, param); 744 | } else { 745 | // we're a client, we're just happy about life and getting events! 746 | // we still attach a screen for the "server", so the mouse warp is easier 747 | ts_display_place( 748 | new_display, 749 | ts_master_get_main(r->mux->master), param); 750 | } 751 | } break; 752 | case 'm': { // mouse move 753 | if (r->proxy) 754 | break; 755 | ts_display_movemouse(ts_master_get_main(r->mux->master), x, y); 756 | } break; 757 | case 'b': { // mouse move 758 | if (r->proxy) 759 | break; 760 | ts_display_button(ts_master_get_main(r->mux->master), b, d); 761 | } break; 762 | case 'w': { // mouse wheel 763 | if (r->proxy) 764 | break; 765 | ts_display_wheel(ts_master_get_main(r->mux->master), b, y, x); 766 | } break; 767 | case 'k': { // key 768 | if (r->proxy) 769 | break; 770 | ts_display_key(ts_master_get_main(r->mux->master), k, d); 771 | } break; 772 | case 'e': { // enter 773 | if (r->proxy) 774 | break; 775 | ts_display_p dd = ts_master_get_main(r->mux->master); 776 | if (dd) { 777 | // update the coordinate on the fake "old" screen, so mouse warping works 778 | dd->master->mousex = dd->bounds.x + x; 779 | dd->master->mousey = dd->bounds.y + y; 780 | ts_display_enter(dd); 781 | } 782 | } break; 783 | case 'l': { // leave 784 | if (r->proxy) 785 | break; 786 | ts_display_leave(ts_master_get_main(r->mux->master)); 787 | } break; 788 | case 'g': { // getclipboard 789 | V3("%s get clipboard\n", __func__); 790 | if (r->proxy) 791 | break; 792 | ts_display_p target = ts_master_display_get(r->mux->master, name); 793 | ts_display_getclipboard( 794 | ts_master_get_main(r->mux->master), 795 | target); 796 | } break; 797 | case 'c': { // clear clipboard 798 | V3("%s clear clipboard\n", __func__); 799 | ts_display_p target = name ? 800 | ts_master_display_get(r->mux->master, name) : 801 | ts_master_get_main(r->mux->master); 802 | if (target) 803 | ts_clipboard_clear(&target->clipboard); 804 | } break; 805 | case 'f': { // clipboard flavor 806 | V3("%s clipboard flavor\n", __func__); 807 | ts_display_p target = name ? 808 | ts_master_display_get(r->mux->master, name) : 809 | ts_master_get_main(r->mux->master); 810 | if (target) 811 | ts_clipboard_add(&target->clipboard, flavor, (uint8_t*)data, strlen(data)); 812 | } break; 813 | case 's': { // set clipboard 814 | V3("%s set clipboard\n", __func__); 815 | ts_display_p target = name ? 816 | ts_master_display_get(r->mux->master, name) : 817 | ts_master_get_main(r->mux->master); 818 | if (target) 819 | ts_display_setclipboard( 820 | ts_master_get_main(r->mux->master), 821 | &target->clipboard); 822 | } break; 823 | default: 824 | V1("%s unknown packet kind '%c'\n", __func__, kind); 825 | break; 826 | } 827 | } 828 | 829 | /* 830 | * read some data, we first save it in a growing buffer, until we find a zero. 831 | * Then we process that packet and continue on for each line. 832 | */ 833 | static int 834 | data_event_read( 835 | struct ts_remote_t * r) 836 | { 837 | uint8_t in[256]; 838 | ssize_t ss = 0; 839 | do { 840 | ss = read(r->socket, in, sizeof(in)); 841 | 842 | /* 843 | * Error, or disconnect, we drop this link 844 | */ 845 | if (ss <= 0) { 846 | if (r->restart) 847 | r->restart(r); 848 | //sleep(1); 849 | return -1; 850 | } 851 | /* 852 | * Try to add this in to the 'current' buffer, grow it if necessary 853 | */ 854 | //printf("%s in len %d, ss %d\n",__func__, r->in_len, (int)ss); 855 | if (r->in_len + ss > r->in_size) { 856 | int news = (r->in_len + ss + 128) & ~127; 857 | r->in = realloc(r->in, news); 858 | V3("%s reallocated in from %d to %d\n", __func__, r->in_size, news); 859 | r->in_size = news; 860 | } 861 | memcpy(r->in + r->in_len, in, ss); 862 | r->in_len += ss; 863 | 864 | /* 865 | * try to find 'packets' from the current input buffer 866 | */ 867 | int packet_len = 0; 868 | do { 869 | packet_len = 0; 870 | 871 | for (int bi = 0; bi < r->in_len; bi++) 872 | if (r->in[bi] == 0) { 873 | packet_len = bi; 874 | break; 875 | } 876 | 877 | /* 878 | * if a packet is found, move the remaining and continue 879 | */ 880 | if (packet_len) { 881 | // got a packet 882 | // printf("%s found a packet %d size (%d in in)\n", __func__, packet_len, r->in_len); 883 | 884 | r->in[packet_len] = 0; 885 | if (packet_len > 0) 886 | data_process_packet(r, r->in, packet_len); 887 | /* 888 | * eat up any remaining line terminations etc 889 | */ 890 | packet_len++; 891 | while (packet_len < r->in_len && r->in[packet_len] < ' ') 892 | packet_len++; 893 | /* move remaining bytes */ 894 | if (r->in_len > packet_len) 895 | memmove(r->in, 896 | r->in + packet_len, 897 | r->in_len - packet_len); 898 | r->in_len -= packet_len; 899 | // printf("%s processed packet, %d remains\n", __func__, r->in_len); 900 | } 901 | } while (packet_len); 902 | 903 | } while (ss == sizeof(in)); 904 | return 0; 905 | } 906 | 907 | /* 908 | * Starts a listen socket 909 | */ 910 | static int 911 | listen_start( 912 | struct ts_remote_t * r) 913 | { 914 | int skt = socket(AF_INET, SOCK_STREAM, 0); 915 | if (skt < 0) 916 | return -1; 917 | int optval = 1; 918 | setsockopt(skt, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); 919 | 920 | size_t addrLen = sizeof(r->addr); 921 | 922 | if (bind(skt, (struct sockaddr*)&r->addr, addrLen) < 0) { 923 | perror("listen_start bind"); 924 | close(skt); 925 | return -1; 926 | } 927 | if (listen(skt, 10) < 0) { 928 | perror("listen_start listen"); 929 | close(skt); 930 | return -1; 931 | } 932 | r->state = skt_state_Listen; 933 | r->socket = skt; 934 | 935 | { // make it nonblocking 936 | int flags = fcntl(skt, F_GETFL, 0); 937 | fcntl(skt, F_SETFL, flags | O_NONBLOCK); 938 | } 939 | return 0; 940 | } 941 | 942 | /* 943 | * failure on a listen socket is bad news anyway 944 | */ 945 | static int 946 | listen_restart( 947 | struct ts_remote_t * r) 948 | { 949 | // we could close the socket and force a start() here... 950 | printf("%s can't recover easily from that. dying\n", __FUNCTION__); 951 | exit(1); 952 | } 953 | 954 | /* 955 | * A 'read' event on a listen socket means we for a connection, so accept it, 956 | * generate a new 'data' remote structure, and add it to the mux 957 | */ 958 | static int 959 | listen_event_read( 960 | struct ts_remote_t * r) 961 | { 962 | V2("%s accepting on %d\n", __func__, r->socket); 963 | struct sockaddr_in addr; 964 | socklen_t addrLen = sizeof(addr); 965 | int fd = accept(r->socket, (struct sockaddr*)&addr, &addrLen); 966 | if (fd <= 0) { 967 | perror("_vdmsg_funnel_event_listen accept"); 968 | exit(1); 969 | // return -1; 970 | } 971 | int i = 1; 972 | setsockopt (fd, IPPROTO_TCP, TCP_NODELAY, &i, sizeof (i)); 973 | 974 | ts_remote_p res = malloc(sizeof(ts_remote_t)); 975 | memset(res, 0, sizeof(*res)); 976 | res->addr = addr; 977 | res->accept_socket = fd; 978 | res->start = data_start; 979 | res->restart = data_restart; 980 | res->can_write = data_can_write; 981 | res->data_read = data_event_read; 982 | res->data_write = data_event_write; 983 | res->mux = r->mux; 984 | 985 | ts_mux_register(res); 986 | 987 | return 0; 988 | } 989 | 990 | /* 991 | * Create a new remote on the current mux. It can be either a listen one 992 | * (if address is NULL) or an outgoing one if it isn't NULL. 993 | */ 994 | int 995 | ts_mux_port_new( 996 | ts_mux_p mux, 997 | ts_master_p master, 998 | char * address, 999 | ts_display_p display) 1000 | { 1001 | ts_remote_p res = malloc(sizeof(ts_remote_t)); 1002 | memset(res, 0, sizeof(*res)); 1003 | 1004 | res->mux = mux; 1005 | res->addr.sin_family = AF_INET; 1006 | res->addr.sin_addr.s_addr = INADDR_ANY; 1007 | res->addr.sin_port = htons(1869); 1008 | res->display = display; 1009 | 1010 | if (address) { 1011 | char *port = strchr(address, ':'); 1012 | if (port) { 1013 | *port = 0; port++; 1014 | res->addr.sin_port = htons(atoi(port)); 1015 | } 1016 | struct hostent *hp = gethostbyname(address); 1017 | if (!hp) { 1018 | fprintf(stderr, "%s unknown host '%s'\n", __func__, address); 1019 | exit(1); 1020 | } 1021 | res->addr.sin_addr.s_addr = *(in_addr_t *) (hp->h_addr_list[0]); 1022 | 1023 | res->start = connect_start; 1024 | res->restart = connect_restart; 1025 | res->can_read = connect_can_read; 1026 | res->can_write = connect_can_write; 1027 | res->data_read = data_event_read; 1028 | res->data_write = data_event_write; 1029 | } else { 1030 | res->start = listen_start; 1031 | res->restart = listen_restart; 1032 | res->data_read = listen_event_read; 1033 | } 1034 | 1035 | // start the mux, if it wasn't there already 1036 | ts_mux_start(mux, master); 1037 | // register the remote with the mux 1038 | ts_mux_register(res); 1039 | 1040 | return -1; 1041 | } 1042 | 1043 | --------------------------------------------------------------------------------