diff --git a/src/org/kde/kdeconnect/Plugins/PresenterPlugin/PresenterActivity.java b/src/org/kde/kdeconnect/Plugins/PresenterPlugin/PresenterActivity.java index f36aca49..c7999361 100644 --- a/src/org/kde/kdeconnect/Plugins/PresenterPlugin/PresenterActivity.java +++ b/src/org/kde/kdeconnect/Plugins/PresenterPlugin/PresenterActivity.java @@ -1,205 +1,201 @@ /* * Copyright 2014 Ahmed I. Khalil * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.kde.kdeconnect.Plugins.PresenterPlugin; import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorEventListener2; import android.hardware.SensorManager; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.PowerManager; import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.PlaybackStateCompat; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.UserInterface.ThemeUtil; import org.kde.kdeconnect_tp.R; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; import androidx.media.VolumeProviderCompat; import static androidx.core.math.MathUtils.clamp; public class PresenterActivity extends AppCompatActivity implements SensorEventListener { private MediaSessionCompat mMediaSession; private PresenterPlugin plugin; private SensorManager sensorManager; - private float xPos, yPos; static final float SENSITIVITY = 0.05f; //TODO: Make configurable? public void gyroscopeEvent(SensorEvent event) { - xPos += -event.values[2] * SENSITIVITY; - yPos += -event.values[0] * SENSITIVITY; - - xPos = clamp(xPos, -1.f, 1.f); - yPos = clamp(yPos, -1.f, 1.f); + float xPos = -event.values[2] * SENSITIVITY; + float yPos = -event.values[0] * SENSITIVITY; plugin.sendPointer(xPos, yPos); } public void onSensorChanged(SensorEvent event) { if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) { gyroscopeEvent(event); } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { //Ignored } void enablePointer() { if (sensorManager != null) { return; //Already enabled } sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); findViewById(R.id.pointer_button).setVisibility(View.VISIBLE); findViewById(R.id.pointer_button).setOnTouchListener((v, event) -> { if(event.getAction() == MotionEvent.ACTION_DOWN){ yPos = 0; xPos = 0; sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE), SensorManager.SENSOR_DELAY_GAME); v.performClick(); // The linter complains if this is not called } else if (event.getAction() == MotionEvent.ACTION_UP) { sensorManager.unregisterListener(this); } return true; }); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ThemeUtil.setUserPreferredTheme(this); setContentView(R.layout.activity_presenter); final String deviceId = getIntent().getStringExtra("deviceId"); BackgroundService.RunWithPlugin(this, deviceId, PresenterPlugin.class, plugin -> runOnUiThread(() -> { this.plugin = plugin; findViewById(R.id.next_button).setOnClickListener(v -> plugin.sendNext()); findViewById(R.id.previous_button).setOnClickListener(v -> plugin.sendPrevious()); if (plugin.isPointerSupported()) { enablePointer(); } })); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu_presenter, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch(item.getItemId()) { case R.id.fullscreen: plugin.sendFullscreen(); return true; case R.id.exit_presentation: plugin.sendEsc(); return true; default: return super.onContextItemSelected(item); } } @Override protected void onStart() { super.onStart(); if (mMediaSession != null) { mMediaSession.setActive(true); return; } createMediaSession(); } @Override protected void onStop() { super.onStop(); if (sensorManager != null) { // Make sure we don't leave the listener on sensorManager.unregisterListener(this); } if (mMediaSession != null) { PowerManager pm = (PowerManager) this.getSystemService(Context.POWER_SERVICE); boolean screenOn; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) { screenOn = pm.isInteractive(); } else { screenOn = pm.isScreenOn(); } if (screenOn) { mMediaSession.release(); } // else we are in the lockscreen, keep the mediasession } } private void createMediaSession() { mMediaSession = new MediaSessionCompat(this, "kdeconnect"); mMediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); mMediaSession.setPlaybackState(new PlaybackStateCompat.Builder() .setState(PlaybackStateCompat.STATE_PLAYING, 0, 0) .build()); mMediaSession.setPlaybackToRemote(getVolumeProvider()); mMediaSession.setActive(true); } private VolumeProviderCompat getVolumeProvider() { final int VOLUME_UP = 1; final int VOLUME_DOWN = -1; return new VolumeProviderCompat(VolumeProviderCompat.VOLUME_CONTROL_RELATIVE, 1, 0) { @Override public void onAdjustVolume(int direction) { if (direction == VOLUME_UP) { plugin.sendNext(); } else if (direction == VOLUME_DOWN) { plugin.sendPrevious(); } } }; } } diff --git a/src/org/kde/kdeconnect/Plugins/PresenterPlugin/PresenterPlugin.java b/src/org/kde/kdeconnect/Plugins/PresenterPlugin/PresenterPlugin.java index ff7b0af5..9bec7a08 100644 --- a/src/org/kde/kdeconnect/Plugins/PresenterPlugin/PresenterPlugin.java +++ b/src/org/kde/kdeconnect/Plugins/PresenterPlugin/PresenterPlugin.java @@ -1,126 +1,132 @@ /* * Copyright 2014 Ahmed I. Khalil * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.kde.kdeconnect.Plugins.PresenterPlugin; import android.app.Activity; import android.content.Intent; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Message; import android.view.KeyEvent; import org.kde.kdeconnect.NetworkPacket; import org.kde.kdeconnect.Plugins.Plugin; import org.kde.kdeconnect.Plugins.PluginFactory; import org.kde.kdeconnect_tp.R; import androidx.core.content.ContextCompat; import static org.kde.kdeconnect.Plugins.MousePadPlugin.KeyListenerView.SpecialKeysMap; @PluginFactory.LoadablePlugin public class PresenterPlugin extends Plugin { private final static String PACKET_TYPE_PRESENTER = "kdeconnect.presenter"; private final static String PACKET_TYPE_MOUSEPAD_REQUEST = "kdeconnect.mousepad.request"; public boolean isPointerSupported() { return device.supportsPacketType(PACKET_TYPE_PRESENTER); } @Override public String getDisplayName() { return context.getString(R.string.pref_plugin_presenter); } @Override public String getDescription() { return context.getString(R.string.pref_plugin_presenter_desc); } @Override public Drawable getIcon() { return ContextCompat.getDrawable(context, R.drawable.ic_presenter); } @Override public boolean hasSettings() { return false; } @Override public boolean hasMainActivity() { return true; } @Override public void startMainActivity(Activity parentActivity) { Intent intent = new Intent(parentActivity, PresenterActivity.class); intent.putExtra("deviceId", device.getDeviceId()); parentActivity.startActivity(intent); } @Override public String[] getSupportedPacketTypes() { return new String[0]; } @Override public String[] getOutgoingPacketTypes() { return new String[]{PACKET_TYPE_MOUSEPAD_REQUEST, PACKET_TYPE_PRESENTER}; } @Override public String getActionName() { return context.getString(R.string.pref_plugin_presenter); } public void sendNext() { NetworkPacket np = new NetworkPacket(PACKET_TYPE_MOUSEPAD_REQUEST); np.set("specialKey", SpecialKeysMap.get(KeyEvent.KEYCODE_PAGE_DOWN)); device.sendPacket(np); } public void sendPrevious() { NetworkPacket np = new NetworkPacket(PACKET_TYPE_MOUSEPAD_REQUEST); np.set("specialKey", SpecialKeysMap.get(KeyEvent.KEYCODE_PAGE_UP)); device.sendPacket(np); } public void sendFullscreen() { NetworkPacket np = new NetworkPacket(PACKET_TYPE_MOUSEPAD_REQUEST); np.set("specialKey", SpecialKeysMap.get(KeyEvent.KEYCODE_F5)); device.sendPacket(np); } public void sendEsc() { NetworkPacket np = new NetworkPacket(PACKET_TYPE_MOUSEPAD_REQUEST); np.set("specialKey", SpecialKeysMap.get(KeyEvent.KEYCODE_ESCAPE)); device.sendPacket(np); } - public void sendPointer(float percentX, float percentY) { - NetworkPacket np = new NetworkPacket(PACKET_TYPE_PRESENTER); - np.set("px", percentX); - np.set("py", percentY); + public void sendPointer(float xDelta, float yDelta) { + NetworkPacket np = device.getAndRemoveUnsentPacket(NetworkPacket.PACKET_REPLACEID_PRESENTERPOINTER); + if (np == null) { + np = new NetworkPacket(PACKET_TYPE_PRESENTER); + } else { + xDelta += np.getInt("px"); + yDelta += np.getInt("px"); + } + np.set("px", xDelta); + np.set("py", yDelta); device.sendPacket(np, NetworkPacket.PACKET_REPLACEID_PRESENTERPOINTER); } }