diff options
Diffstat (limited to 'app/src/main/java/org')
4 files changed, 413 insertions, 331 deletions
| diff --git a/app/src/main/java/org/connectbot/ConsoleActivity.java b/app/src/main/java/org/connectbot/ConsoleActivity.java index d628a07..e979b34 100644 --- a/app/src/main/java/org/connectbot/ConsoleActivity.java +++ b/app/src/main/java/org/connectbot/ConsoleActivity.java @@ -50,10 +50,9 @@ import android.os.IBinder;  import android.os.Message;  import android.preference.PreferenceManager;  import android.support.annotation.Nullable; -import android.support.v4.app.ActivityCompat;  import android.support.design.widget.TabLayout; +import android.support.v4.app.ActivityCompat;  import android.support.v4.view.MenuItemCompat; -import android.support.v4.view.MotionEventCompat;  import android.support.v4.view.PagerAdapter;  import android.support.v4.view.ViewPager;  import android.support.v7.app.ActionBar; @@ -61,9 +60,6 @@ import android.support.v7.app.AppCompatActivity;  import android.support.v7.widget.Toolbar;  import android.text.ClipboardManager;  import android.util.Log; -import android.view.ContextMenu; -import android.view.GestureDetector; -import android.view.InputDevice;  import android.view.KeyEvent;  import android.view.LayoutInflater;  import android.view.Menu; @@ -74,7 +70,6 @@ import android.view.View;  import android.view.View.OnClickListener;  import android.view.View.OnKeyListener;  import android.view.View.OnTouchListener; -import android.view.ViewConfiguration;  import android.view.ViewGroup;  import android.view.Window;  import android.view.WindowManager; @@ -100,8 +95,6 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne  	protected static final int REQUEST_EDIT = 1; -	private static final int CLICK_TIME = 400; -	private static final float MAX_CLICK_DISTANCE = 25f;  	private static final int KEYBOARD_DISPLAY_TIME = 3000;  	private static final int KEYBOARD_REPEAT_INITIAL = 500;  	private static final int KEYBOARD_REPEAT = 100; @@ -140,7 +133,6 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne  	private Animation fade_out_delayed;  	private Animation keyboard_fade_in, keyboard_fade_out; -	private float lastX, lastY;  	private InputMethodManager inputManager; @@ -498,8 +490,9 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne  		inflater = LayoutInflater.from(this);  		toolbar = (Toolbar) findViewById(R.id.toolbar); +  		pager = (ViewPager) findViewById(R.id.console_flip); -		registerForContextMenu(pager); +  		pager.addOnPageChangeListener(  				new ViewPager.SimpleOnPageChangeListener() {  					@Override @@ -669,255 +662,81 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne  		if (tabs != null)  			setupTabLayoutWithViewPager(); -		// detect fling gestures to switch between terminals -		final GestureDetector detect = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() { -			private float totalY = 0; - -			@Override -			public void onLongPress(MotionEvent e) { -				super.onLongPress(e); -				openContextMenu(pager); -			} - +		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { +			pager.setOnTouchListener(new OnTouchListener() { +				public boolean onTouch(View v, MotionEvent event) { +					TerminalBridge bridge = adapter.getCurrentTerminalView().bridge; -			@Override -			public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - -				// if copying, then ignore -				if (copySource != null && copySource.isSelectingForCopy()) -					return false; - -				if (e1 == null || e2 == null) -					return false; - -				// if releasing then reset total scroll -				if (e2.getAction() == MotionEvent.ACTION_UP) { -					totalY = 0; -				} - -				// activate consider if within x tolerance -				int touchSlop = ViewConfiguration.get(ConsoleActivity.this).getScaledTouchSlop(); -				if (Math.abs(e1.getX() - e2.getX()) < touchSlop * 4) { - -					View view = adapter.getCurrentTerminalView(); -					if (view == null) return false; -					TerminalView terminal = (TerminalView) view; - -					// estimate how many rows we have scrolled through -					// accumulate distance that doesn't trigger immediate scroll -					totalY += distanceY; -					final int moved = (int) (totalY / terminal.bridge.charHeight); - -					// consume as scrollback only if towards right half of screen -					if (e2.getX() > view.getWidth() / 2) { -						if (moved != 0) { -							int base = terminal.bridge.buffer.getWindowBase(); -							terminal.bridge.buffer.setWindowBase(base + moved); -							totalY = 0; -							return true; -						} -					} else { -						// otherwise consume as pgup/pgdown for every 5 lines -						if (moved > 5) { -							((vt320) terminal.bridge.buffer).keyPressed(vt320.KEY_PAGE_DOWN, ' ', 0); -							terminal.bridge.tryKeyVibrate(); -							totalY = 0; -							return true; -						} else if (moved < -5) { -							((vt320) terminal.bridge.buffer).keyPressed(vt320.KEY_PAGE_UP, ' ', 0); -							terminal.bridge.tryKeyVibrate(); -							totalY = 0; -							return true; -						} +					boolean isCopyingInProgress = +						(copySource != null && copySource.isSelectingForCopy()); +					if (!isCopyingInProgress && keyboardGroup.getVisibility() == View.GONE) { +						showEmulatedKeys(true);  					} -				} - -				return false; -			} - - -		}); - -		pager.setLongClickable(true); -		pager.setOnTouchListener(new OnTouchListener() { - -			public boolean onTouch(View v, MotionEvent event) { -				TerminalBridge bridge = adapter.getCurrentTerminalView().bridge; +					// when copying, highlight the area +					if (isCopyingInProgress) { +						SelectionArea area = copySource.getSelectionArea(); +						int row = (int) Math.floor(event.getY() / bridge.charHeight); +						int col = (int) Math.floor(event.getX() / bridge.charWidth); -				// Handle mouse-specific actions. -				if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH && -						MotionEventCompat.getSource(event) == InputDevice.SOURCE_MOUSE) { -					if (onMouseEvent(event, bridge)) { -						return true; -					} -				} +						switch (event.getAction()) { +						case MotionEvent.ACTION_DOWN: +							// recording starting area +							if (area.isSelectingOrigin()) { +								area.setRow(row); +								area.setColumn(col); +								lastTouchRow = row; +								lastTouchCol = col; +								copySource.redraw(); +							} +							return true; +						case MotionEvent.ACTION_MOVE: +							/* ignore when user hasn't moved since last time so +							 * we can fine-tune with directional pad +							 */ +							if (row == lastTouchRow && col == lastTouchCol) +								return true; -				// when copying, highlight the area -				if (copySource != null && copySource.isSelectingForCopy()) { -					SelectionArea area = copySource.getSelectionArea(); -					int row = (int) Math.floor(event.getY() / bridge.charHeight); -					int col = (int) Math.floor(event.getX() / bridge.charWidth); +							// if the user moves, start the selection for other corner +							area.finishSelectingOrigin(); -					switch (event.getAction()) { -					case MotionEvent.ACTION_DOWN: -						// recording starting area -						if (area.isSelectingOrigin()) { +							// update selected area  							area.setRow(row);  							area.setColumn(col);  							lastTouchRow = row;  							lastTouchCol = col;  							copySource.redraw(); -						} -						return true; -					case MotionEvent.ACTION_MOVE: -						/* ignore when user hasn't moved since last time so -						 * we can fine-tune with directional pad -						 */ -						if (row == lastTouchRow && col == lastTouchCol) -							return true; - -						// if the user moves, start the selection for other corner -						area.finishSelectingOrigin(); - -						// update selected area -						area.setRow(row); -						area.setColumn(col); -						lastTouchRow = row; -						lastTouchCol = col; -						copySource.redraw(); -						return true; -					case MotionEvent.ACTION_UP: -						/* If they didn't move their finger, maybe they meant to -						 * select the rest of the text with the directional pad. -						 */ -						if (area.getLeft() == area.getRight() && -								area.getTop() == area.getBottom()) {  							return true; -						} - -						// copy selected area to clipboard -						String copiedText = area.copyFrom(copySource.buffer); - -						clipboard.setText(copiedText); -						Toast.makeText(ConsoleActivity.this, getString(R.string.console_copy_done, copiedText.length()), Toast.LENGTH_LONG).show(); -						// fall through to clear state - -					case MotionEvent.ACTION_CANCEL: -						// make sure we clear any highlighted area -						area.reset(); -						copySource.setSelectingForCopy(false); -						copySource.redraw(); -						return true; -					} -				} +						case MotionEvent.ACTION_UP: +							/* If they didn't move their finger, maybe they meant to +							 * select the rest of the text with the directional pad. +							 */ +							if (area.getLeft() == area.getRight() && +									area.getTop() == area.getBottom()) { +								return true; +							} -				if (event.getAction() == MotionEvent.ACTION_DOWN) { -					lastX = event.getX(); -					lastY = event.getY(); -				} else if (event.getAction() == MotionEvent.ACTION_UP -						&& keyboardGroup.getVisibility() == View.GONE -						&& event.getEventTime() - event.getDownTime() < CLICK_TIME -						&& Math.abs(event.getX() - lastX) < MAX_CLICK_DISTANCE -						&& Math.abs(event.getY() - lastY) < MAX_CLICK_DISTANCE) { -					showEmulatedKeys(true); -				} +							// copy selected area to clipboard +							String copiedText = area.copyFrom(copySource.buffer); -				// pass any touch events back to detector -				return detect.onTouchEvent(event); -			} +							clipboard.setText(copiedText); +							Toast.makeText(ConsoleActivity.this, getString(R.string.console_copy_done, copiedText.length()), Toast.LENGTH_LONG).show(); +							// fall through to clear state -			/** -			 * @param event -			 * @param bridge -			 * @return True if the event is handled. -			 */ -			@TargetApi(14) -			private boolean onMouseEvent(MotionEvent event, TerminalBridge bridge) { -				int row = (int) Math.floor(event.getY() / bridge.charHeight); -				int col = (int) Math.floor(event.getX() / bridge.charWidth); -				int meta = event.getMetaState(); -				boolean shiftOn = (meta & KeyEvent.META_SHIFT_ON) != 0; -				boolean mouseReport = ((vt320) bridge.buffer).isMouseReportEnabled(); - -				// MouseReport can be "defeated" using the shift key. -				if ((!mouseReport || shiftOn)) { -					if (event.getAction() == MotionEvent.ACTION_DOWN) { -						switch (event.getButtonState()) { -						case MotionEvent.BUTTON_PRIMARY: -							// Automatically start copy mode if using a mouse. -							startCopyMode(); -							break; -						case MotionEvent.BUTTON_SECONDARY: -							openContextMenu(pager); -							return true; -						case MotionEvent.BUTTON_TERTIARY: -							// Middle click pastes. -							pasteIntoTerminal(); +						case MotionEvent.ACTION_CANCEL: +							// make sure we clear any highlighted area +							area.reset(); +							copySource.setSelectingForCopy(false); +							copySource.redraw();  							return true;  						}  					} -				} else if (event.getAction() == MotionEvent.ACTION_DOWN) { -					((vt320) bridge.buffer).mousePressed( -							col, row, mouseEventToJavaModifiers(event)); -					return true; -				} else if (event.getAction() == MotionEvent.ACTION_UP) { -					((vt320) bridge.buffer).mouseReleased(col, row); -					return true; -				} else if (event.getAction() == MotionEvent.ACTION_MOVE) { -					int buttonState = event.getButtonState(); -					int button = (buttonState & MotionEvent.BUTTON_PRIMARY) != 0 ? 0 : -							(buttonState & MotionEvent.BUTTON_SECONDARY) != 0 ? 1 : -									(buttonState & MotionEvent.BUTTON_TERTIARY) != 0 ? 2 : 3; -					((vt320) bridge.buffer).mouseMoved( -							button, -							col, -							row, -							(meta & KeyEvent.META_CTRL_ON) != 0, -							(meta & KeyEvent.META_SHIFT_ON) != 0, -							(meta & KeyEvent.META_META_ON) != 0); +  					return true;  				} - -				return false; -			} - -		}); -	} - -	/** -	 * Takes an android mouse event and produces a Java InputEvent modifiers int which can be -	 * passed to vt320. -	 * @param mouseEvent The {@link MotionEvent} which should be a mouse click or release. -	 * @return A Java InputEvent modifier int. See -	 * http://docs.oracle.com/javase/7/docs/api/java/awt/event/InputEvent.html -	 */ -	@TargetApi(14) -	private static int mouseEventToJavaModifiers(MotionEvent mouseEvent) { -		if (MotionEventCompat.getSource(mouseEvent) != InputDevice.SOURCE_MOUSE) return 0; - -		int mods = 0; - -		// See http://docs.oracle.com/javase/7/docs/api/constant-values.html -		int buttonState = mouseEvent.getButtonState(); -		if ((buttonState & MotionEvent.BUTTON_PRIMARY) != 0) -			mods |= 16; -		if ((buttonState & MotionEvent.BUTTON_SECONDARY) != 0) -			mods |= 8; -		if ((buttonState & MotionEvent.BUTTON_TERTIARY) != 0) -			mods |= 4; - -		// Note: Meta and Ctrl are intentionally swapped here to keep logic in vt320 simple. -		int meta = mouseEvent.getMetaState(); -		if ((meta & KeyEvent.META_META_ON) != 0) -			mods |= 2; -		if ((meta & KeyEvent.META_SHIFT_ON) != 0) -			mods |= 1; -		if ((meta & KeyEvent.META_CTRL_ON) != 0) -			mods |= 4; - -		return mods; +			}); +		}  	}  	/** @@ -1011,19 +830,21 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne  			}  		}); -		copy = menu.add(R.string.console_menu_copy); -		if (hardKeyboard) -			copy.setAlphabeticShortcut('c'); -		MenuItemCompat.setShowAsAction(copy, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); -		copy.setIcon(R.drawable.ic_action_copy); -		copy.setEnabled(activeTerminal); -		copy.setOnMenuItemClickListener(new OnMenuItemClickListener() { -			public boolean onMenuItemClick(MenuItem item) { -				startCopyMode(); -				Toast.makeText(ConsoleActivity.this, getString(R.string.console_copy_start), Toast.LENGTH_LONG).show(); -				return true; -			} -		}); +		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { +			copy = menu.add(R.string.console_menu_copy); +			if (hardKeyboard) +				copy.setAlphabeticShortcut('c'); +			MenuItemCompat.setShowAsAction(copy, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); +			copy.setIcon(R.drawable.ic_action_copy); +			copy.setEnabled(activeTerminal); +			copy.setOnMenuItemClickListener(new OnMenuItemClickListener() { +				public boolean onMenuItemClick(MenuItem item) { +					startCopyMode(); +					Toast.makeText(ConsoleActivity.this, getString(R.string.console_copy_start), Toast.LENGTH_LONG).show(); +					return true; +				} +			}); +		}  		paste = menu.add(R.string.console_menu_paste);  		if (hardKeyboard) @@ -1144,7 +965,10 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne  			disconnect.setTitle(R.string.list_host_disconnect);  		else  			disconnect.setTitle(R.string.console_menu_close); -		copy.setEnabled(activeTerminal); + +		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { +			copy.setEnabled(activeTerminal); +		}  		paste.setEnabled(clipboard.hasText() && sessionOpen);  		portForward.setEnabled(sessionOpen && canForwardPorts);  		urlscan.setEnabled(activeTerminal); @@ -1174,32 +998,6 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne  	}  	@Override -	public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { -		final TerminalView view = adapter.getCurrentTerminalView(); -		boolean activeTerminal = view != null; -		boolean sessionOpen = false; - -		if (activeTerminal) { -			TerminalBridge bridge = view.bridge; -			sessionOpen = bridge.isSessionOpen(); -		} - -		MenuItem paste = menu.add(R.string.console_menu_paste); -		if (hardKeyboard) -			paste.setAlphabeticShortcut('v'); -		paste.setIcon(android.R.drawable.ic_menu_edit); -		paste.setEnabled(clipboard.hasText() && sessionOpen); -		paste.setOnMenuItemClickListener(new OnMenuItemClickListener() { -			public boolean onMenuItemClick(MenuItem item) { -				pasteIntoTerminal(); -				return true; -			} -		}); - - -	} - -	@Override  	public void onStart() {  		super.onStart(); @@ -1308,6 +1106,9 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne  		super.onSaveInstanceState(savedInstanceState);  	} +	/** +	 * Only intended for pre-Honeycomb devices. +	 */  	private void startCopyMode() {  		// mark as copying and reset any previous bounds  		TerminalView terminalView = (TerminalView) adapter.getCurrentTerminalView(); @@ -1494,7 +1295,7 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne  			overlay.setText(bridge.host.getNickname());  			// and add our terminal view control, using index to place behind overlay -			final TerminalView terminal = new TerminalView(container.getContext(), bridge); +			final TerminalView terminal = new TerminalView(container.getContext(), bridge, pager);  			terminal.setId(R.id.terminal_view);  			view.addView(terminal, 0); @@ -1572,7 +1373,9 @@ public class ConsoleActivity extends AppCompatActivity implements BridgeDisconne  		public TerminalView getCurrentTerminalView() {  			View currentView = pager.findViewWithTag(getBridgeAtPosition(pager.getCurrentItem())); -			if (currentView == null) return null; +			if (currentView == null) { +				return null; +			}  			return (TerminalView) currentView.findViewById(R.id.terminal_view);  		}  	} diff --git a/app/src/main/java/org/connectbot/TerminalView.java b/app/src/main/java/org/connectbot/TerminalView.java index 7c4f51f..6d051f6 100644 --- a/app/src/main/java/org/connectbot/TerminalView.java +++ b/app/src/main/java/org/connectbot/TerminalView.java @@ -27,6 +27,7 @@ import org.connectbot.service.TerminalBridge;  import org.connectbot.service.TerminalKeyListener;  import android.annotation.TargetApi; +import android.annotation.SuppressLint;  import android.content.ContentResolver;  import android.content.Context;  import android.content.Intent; @@ -38,11 +39,19 @@ import android.graphics.Paint;  import android.graphics.Path;  import android.graphics.PixelXorXfermode;  import android.graphics.RectF; +import android.graphics.Typeface;  import android.net.Uri;  import android.os.AsyncTask; +import android.os.Build;  import android.support.v4.view.MotionEventCompat; +import android.support.v4.view.ViewPager; +import android.text.ClipboardManager; +import android.view.ActionMode; +import android.view.GestureDetector;  import android.view.InputDevice;  import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem;  import android.view.MotionEvent;  import android.view.View;  import android.view.ViewGroup.LayoutParams; @@ -51,6 +60,7 @@ import android.view.accessibility.AccessibilityManager;  import android.view.inputmethod.BaseInputConnection;  import android.view.inputmethod.EditorInfo;  import android.view.inputmethod.InputConnection; +import android.widget.TextView;  import android.widget.Toast;  import de.mud.terminal.VDUBuffer;  import de.mud.terminal.vt320; @@ -62,10 +72,18 @@ import de.mud.terminal.vt320;   *   * @author jsharkey   */ -public class TerminalView extends View implements FontSizeChangedListener { +public class TerminalView extends TextView implements FontSizeChangedListener {  	private final Context context;  	public final TerminalBridge bridge; + +	private final ViewPager viewPager; +	private GestureDetector gestureDetector; + +	private ClipboardManager clipboard; +	private ActionMode selectionActionMode = null; +	private String currentSelection = ""; +  	private final Paint paint;  	private final Paint cursorPaint;  	private final Paint cursorStrokePaint; @@ -96,17 +114,19 @@ public class TerminalView extends View implements FontSizeChangedListener {  	private static final String SCREENREADER_INTENT_ACTION = "android.accessibilityservice.AccessibilityService";  	private static final String SCREENREADER_INTENT_CATEGORY = "android.accessibilityservice.category.FEEDBACK_SPOKEN"; -	public TerminalView(Context context, TerminalBridge bridge) { +	public TerminalView(Context context, TerminalBridge bridge, ViewPager pager) {  		super(context);  		this.context = context;  		this.bridge = bridge; -		paint = new Paint(); +		this.viewPager = pager;  		setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));  		setFocusable(true);  		setFocusableInTouchMode(true); +		paint = new Paint(); +  		cursorPaint = new Paint();  		cursorPaint.setColor(bridge.color[bridge.defaultFg]);  		cursorPaint.setXfermode(new PixelXorXfermode(bridge.color[bridge.defaultBg])); @@ -142,6 +162,7 @@ public class TerminalView extends View implements FontSizeChangedListener {  		scaleMatrix = new Matrix();  		bridge.addFontSizeChangedListener(this); +		bridge.terminalView = this;  		// connect our view up to the bridge  		setOnKeyListener(bridge.getKeyHandler()); @@ -150,6 +171,261 @@ public class TerminalView extends View implements FontSizeChangedListener {  		// Enable accessibility features if a screen reader is active.  		new AccessibilityStateTester().execute((Void) null); + +		clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + +		setTextColor(0x00000000); +		setTypeface(Typeface.MONOSPACE); +		onFontSizeChanged(bridge.getFontSize()); + +		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { +			setTextIsSelectable(true); + +			this.setCustomSelectionActionModeCallback(new ActionMode.Callback() { +				private static final int PASTE = 0; + +				@Override +				@SuppressLint("NewApi") +				public boolean onCreateActionMode(ActionMode mode, Menu menu) { +					TerminalView.this.selectionActionMode = mode; + +					menu.add(0, PASTE, 2, "Paste") +							.setIcon(R.drawable.ic_action_paste) +							.setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT | MenuItem.SHOW_AS_ACTION_ALWAYS); + +					return true; +				} + +				@Override +				@SuppressLint("NewApi") +				public boolean onActionItemClicked(ActionMode mode, MenuItem item) { +					if (item.getItemId() == PASTE) { +						String clip = clipboard.getText().toString(); +						TerminalView.this.bridge.injectString(clip); +						mode.finish(); +						return true; +					} + +					return false; +				} + +				@Override +				public boolean onPrepareActionMode(ActionMode mode, Menu menu) { +					return false; +				} + +				@Override +				public void onDestroyActionMode(ActionMode mode) { +				} +			}); + +			gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { +				private TerminalBridge bridge = TerminalView.this.bridge; +				private float totalY = 0; + +				@Override +				public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { +					// if releasing then reset total scroll +					if (e2.getAction() == MotionEvent.ACTION_UP) { +						totalY = 0; +					} + +					totalY += distanceY; +					final int moved = (int) (totalY / bridge.charHeight); + +					if (moved != 0) { +						int base = bridge.buffer.getWindowBase(); +						bridge.buffer.setWindowBase(base + moved); +						totalY = 0; + +						copyBufferToText(); +					} + +					return true; +				} + +				@Override +				public boolean onSingleTapConfirmed(MotionEvent e) { +					viewPager.performClick(); +					return super.onSingleTapConfirmed(e); +				} +			}); +		} +	} + +	public void copyCurrentSelectionToClipboard() { +		ClipboardManager clipboard = +				(ClipboardManager) TerminalView.this.context.getSystemService(Context.CLIPBOARD_SERVICE); +		if (currentSelection.length() != 0) { +			clipboard.setText(currentSelection); +		} + +		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && +			selectionActionMode != null) { +			selectionActionMode.finish(); +			selectionActionMode = null; +		} +	} + +	@Override +	protected void onSelectionChanged(int selStart, int selEnd) { +		currentSelection = getText().toString().substring(selStart, selEnd); +		super.onSelectionChanged(selStart, selEnd); +	} + +	@Override +	public boolean performLongClick() { +		copyBufferToText(); +		return super.performLongClick(); +	} + +	@Override +	public boolean onTouchEvent(MotionEvent event) { +		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { +			return false; +		} + +		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH && +				MotionEventCompat.getSource(event) == InputDevice.SOURCE_MOUSE) { +			if (onMouseEvent(event, bridge)) { +				return true; +			} +		} + +		super.onTouchEvent(event); +		if (gestureDetector != null) { +			gestureDetector.onTouchEvent(event); +		} + +		return true; +	} + +	/** +	 * @param event +	 * @param bridge +	 * @return True if the event is handled. +	 */ +	@TargetApi(14) +	private boolean onMouseEvent(MotionEvent event, TerminalBridge bridge) { +		int row = (int) Math.floor(event.getY() / bridge.charHeight); +		int col = (int) Math.floor(event.getX() / bridge.charWidth); +		int meta = event.getMetaState(); +		boolean shiftOn = (meta & KeyEvent.META_SHIFT_ON) != 0; +		boolean mouseReport = ((vt320) bridge.buffer).isMouseReportEnabled(); + +		// MouseReport can be "defeated" using the shift key. +		if ((!mouseReport || shiftOn)) { +			if (event.getAction() == MotionEvent.ACTION_DOWN) { +				switch (event.getButtonState()) { +				case MotionEvent.BUTTON_TERTIARY: +					// Middle click pastes. +					String clip = clipboard.getText().toString(); +					bridge.injectString(clip); +					return true; +				} +			} +		} else if (event.getAction() == MotionEvent.ACTION_DOWN) { +			((vt320) bridge.buffer).mousePressed( +					col, row, mouseEventToJavaModifiers(event)); +			return true; +		} else if (event.getAction() == MotionEvent.ACTION_UP) { +			((vt320) bridge.buffer).mouseReleased(col, row); +			return true; +		} else if (event.getAction() == MotionEvent.ACTION_MOVE) { +			int buttonState = event.getButtonState(); +			int button = (buttonState & MotionEvent.BUTTON_PRIMARY) != 0 ? 0 : +					(buttonState & MotionEvent.BUTTON_SECONDARY) != 0 ? 1 : +							(buttonState & MotionEvent.BUTTON_TERTIARY) != 0 ? 2 : 3; +			((vt320) bridge.buffer).mouseMoved( +					button, +					col, +					row, +					(meta & KeyEvent.META_CTRL_ON) != 0, +					(meta & KeyEvent.META_SHIFT_ON) != 0, +					(meta & KeyEvent.META_META_ON) != 0); +			return true; +		} + +		return false; +	} + +	/** +	 * Takes an android mouse event and produces a Java InputEvent modifiers int which can be +	 * passed to vt320. +	 * @param mouseEvent The {@link MotionEvent} which should be a mouse click or release. +	 * @return A Java InputEvent modifier int. See +	 * http://docs.oracle.com/javase/7/docs/api/java/awt/event/InputEvent.html +	 */ +	@TargetApi(14) +	private static int mouseEventToJavaModifiers(MotionEvent mouseEvent) { +		if (MotionEventCompat.getSource(mouseEvent) != InputDevice.SOURCE_MOUSE) return 0; + +		int mods = 0; + +		// See http://docs.oracle.com/javase/7/docs/api/constant-values.html +		int buttonState = mouseEvent.getButtonState(); +		if ((buttonState & MotionEvent.BUTTON_PRIMARY) != 0) +			mods |= 16; +		if ((buttonState & MotionEvent.BUTTON_SECONDARY) != 0) +			mods |= 8; +		if ((buttonState & MotionEvent.BUTTON_TERTIARY) != 0) +			mods |= 4; + +		// Note: Meta and Ctrl are intentionally swapped here to keep logic in vt320 simple. +		int meta = mouseEvent.getMetaState(); +		if ((meta & KeyEvent.META_META_ON) != 0) +			mods |= 2; +		if ((meta & KeyEvent.META_SHIFT_ON) != 0) +			mods |= 1; +		if ((meta & KeyEvent.META_CTRL_ON) != 0) +			mods |= 4; + +		return mods; +	} + +	@Override +	@TargetApi(12) +	public boolean onGenericMotionEvent(MotionEvent event) { +		if ((MotionEventCompat.getSource(event) & InputDevice.SOURCE_CLASS_POINTER) != 0) { +			switch (event.getAction()) { +			case MotionEvent.ACTION_SCROLL: +				// Process scroll wheel movement: +				float yDistance = MotionEventCompat.getAxisValue(event, MotionEvent.AXIS_VSCROLL); +				if (yDistance != 0) { +					int base = bridge.buffer.getWindowBase(); +					bridge.buffer.setWindowBase(base - Math.round(yDistance)); +					return true; +				} +			} +		} +		return super.onGenericMotionEvent(event); +	} + +	private void copyBufferToText() { +		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { +			// It is pointless to run this function because the textView is not selectable pre-Honeycomb. +			return; +		} + +		VDUBuffer vb = bridge.getVDUBuffer(); + +		String line = ""; +		String buffer = ""; + +		int windowBase = vb.getWindowBase(); +		int rowBegin = vb.getTopMargin(); +		int rowEnd = vb.getBottomMargin(); +		int numCols = vb.getColumns() - 1; + +		for (int r = rowBegin; r <= rowEnd; r++) { +			for (int c = 0; c < numCols; c++) { +				line += vb.charArray[windowBase + r][c]; +			} +			buffer += line.replaceAll("\\s+$", "") + "\n"; +			line = ""; +		} + +		setText(buffer);  	}  	public void destroy() { @@ -168,6 +444,9 @@ public class TerminalView extends View implements FontSizeChangedListener {  	public void onFontSizeChanged(float size) {  		scaleCursors(); +		setTextSize(size); +		setLineSpacing(0.0f, 1.1f); // KLUDGE: doesnt work on certain font sizes +		copyBufferToText();  	}  	private void scaleCursors() { @@ -246,19 +525,22 @@ public class TerminalView extends View implements FontSizeChangedListener {  			}  			// draw any highlighted area -			if (bridge.isSelectingForCopy()) { +			if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB && +				bridge.isSelectingForCopy()) {  				SelectionArea area = bridge.getSelectionArea();  				canvas.save(Canvas.CLIP_SAVE_FLAG);  				canvas.clipRect( -					area.getLeft() * bridge.charWidth, -					area.getTop() * bridge.charHeight, -					(area.getRight() + 1) * bridge.charWidth, -					(area.getBottom() + 1) * bridge.charHeight +						area.getLeft() * bridge.charWidth, +						area.getTop() * bridge.charHeight, +						(area.getRight() + 1) * bridge.charWidth, +						(area.getBottom() + 1) * bridge.charHeight  				);  				canvas.drawPaint(cursorPaint);  				canvas.restore();  			}  		} + +		super.onDraw(canvas);  	}  	public void notifyUser(String message) { @@ -324,37 +606,6 @@ public class TerminalView extends View implements FontSizeChangedListener {  		};  	} -	@Override -	@TargetApi(12) -	public boolean onGenericMotionEvent(MotionEvent event) { -		if ((MotionEventCompat.getSource(event) & InputDevice.SOURCE_CLASS_POINTER) != 0) { -			switch (event.getAction()) { -			case MotionEvent.ACTION_SCROLL: -				// Process scroll wheel movement: -				float yDistance = MotionEventCompat.getAxisValue(event, MotionEvent.AXIS_VSCROLL); -				boolean mouseReport = ((vt320) bridge.buffer).isMouseReportEnabled(); -				if (mouseReport) { -					int row = (int) Math.floor(event.getY() / bridge.charHeight); -					int col = (int) Math.floor(event.getX() / bridge.charWidth); - -					((vt320) bridge.buffer).mouseWheel( -							yDistance > 0, -							col, -							row, -							(event.getMetaState() & KeyEvent.META_CTRL_ON) != 0, -							(event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0, -							(event.getMetaState() & KeyEvent.META_META_ON) != 0); -					return true; -				} else if (yDistance != 0) { -					int base = bridge.buffer.getWindowBase(); -					bridge.buffer.setWindowBase(base - Math.round(yDistance)); -					return true; -				} -			} -		} -		return super.onGenericMotionEvent(event); -	} -  	public void propagateConsoleText(char[] rawText, int length) {  		if (mAccessibilityActive) {  			synchronized (mAccessibilityLock) { diff --git a/app/src/main/java/org/connectbot/service/TerminalBridge.java b/app/src/main/java/org/connectbot/service/TerminalBridge.java index b9e29e8..b532a20 100644 --- a/app/src/main/java/org/connectbot/service/TerminalBridge.java +++ b/app/src/main/java/org/connectbot/service/TerminalBridge.java @@ -71,6 +71,7 @@ public class TerminalBridge implements VDUDisplay {  	public int defaultBg = HostDatabase.DEFAULT_BG_COLOR;  	protected final TerminalManager manager; +	public TerminalView terminalView;  	public HostBean host; @@ -341,6 +342,33 @@ public class TerminalBridge implements VDUDisplay {  	}  	/** +	 * Only intended for pre-Honeycomb devices. +	 */ +	public void setSelectingForCopy(boolean selectingForCopy) { +		this.selectingForCopy = selectingForCopy; +	} + +	/** +	 * Only intended for pre-Honeycomb devices. +	 */ +	public boolean isSelectingForCopy() { +		return selectingForCopy; +	} + +	/** +	 * Only intended for pre-Honeycomb devices. +	 */ +	public SelectionArea getSelectionArea() { +		return selectionArea; +	} + +	public void copyCurrentSelection() { +		if (terminalView != null) { +			terminalView.copyCurrentSelectionToClipboard(); +		} +	} + +	/**  	 * Inject a specific string into this terminal. Used for post-login strings  	 * and pasting clipboard.  	 */ @@ -482,18 +510,6 @@ public class TerminalBridge implements VDUDisplay {  		}  	} -	public void setSelectingForCopy(boolean selectingForCopy) { -		this.selectingForCopy = selectingForCopy; -	} - -	public boolean isSelectingForCopy() { -		return selectingForCopy; -	} - -	public SelectionArea getSelectionArea() { -		return selectionArea; -	} -  	public synchronized void tryKeyVibrate() {  		manager.tryKeyVibrate();  	} @@ -538,6 +554,10 @@ public class TerminalBridge implements VDUDisplay {  		forcedSize = false;  	} +	public float getFontSize() { +		return fontSizeDp; +	} +  	/**  	 * Add an {@link FontSizeChangedListener} to the list of listeners for this  	 * bridge. diff --git a/app/src/main/java/org/connectbot/service/TerminalKeyListener.java b/app/src/main/java/org/connectbot/service/TerminalKeyListener.java index 1b2ffe4..753fa86 100644 --- a/app/src/main/java/org/connectbot/service/TerminalKeyListener.java +++ b/app/src/main/java/org/connectbot/service/TerminalKeyListener.java @@ -299,6 +299,14 @@ public class TerminalKeyListener implements OnKeyListener, OnSharedPreferenceCha  					return true;  			} +			// CTRL-SHIFT-C to copy. +			if (keyCode == KeyEvent.KEYCODE_C +					&& (derivedMetaState & HC_META_CTRL_ON) != 0 +					&& (derivedMetaState & KeyEvent.META_SHIFT_ON) != 0) { +				bridge.copyCurrentSelection(); +				return true; +			} +  			// CTRL-SHIFT-V to paste.  			if (keyCode == KeyEvent.KEYCODE_V  					&& (derivedMetaState & HC_META_CTRL_ON) != 0 | 
