diff --git a/README.md b/README.md index 1fdb2c83..af67a17b 100644 --- a/README.md +++ b/README.md @@ -267,6 +267,8 @@ you are interested, see [issue 14]. | click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ (`Cmd`+`↓` on MacOS) | | click on `POWER` | `Ctrl`+`p` | | turn screen on | _Right-click²_ | + | expand notification panel | `Ctrl`+`n` | + | collapse notification panel | `Ctrl`+`Shift`+`n` | | paste computer clipboard to device | `Ctrl`+`v` | | enable/disable FPS counter (on stdout) | `Ctrl`+`i` | diff --git a/app/src/control_event.h b/app/src/control_event.h index 471a9a9b..c2569615 100644 --- a/app/src/control_event.h +++ b/app/src/control_event.h @@ -20,7 +20,11 @@ enum control_event_type { CONTROL_EVENT_TYPE_COMMAND, }; -#define CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON 0 +enum control_event_command { + CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON, + CONTROL_EVENT_COMMAND_EXPAND_NOTIFICATION_PANEL, + CONTROL_EVENT_COMMAND_COLLAPSE_NOTIFICATION_PANEL, +}; struct control_event { enum control_event_type type; @@ -44,7 +48,7 @@ struct control_event { Sint32 vscroll; } scroll_event; struct { - int action; + enum control_event_command action; } command_event; }; }; diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 7099249e..ef3d372c 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -94,6 +94,26 @@ static void press_back_or_turn_screen_on(struct controller *controller) { } } +static void expand_notification_panel(struct controller *controller) { + struct control_event control_event; + control_event.type = CONTROL_EVENT_TYPE_COMMAND; + control_event.command_event.action = CONTROL_EVENT_COMMAND_EXPAND_NOTIFICATION_PANEL; + + if (!controller_push_event(controller, &control_event)) { + LOGW("Cannot expand notification panel"); + } +} + +static void collapse_notification_panel(struct controller *controller) { + struct control_event control_event; + control_event.type = CONTROL_EVENT_TYPE_COMMAND; + control_event.command_event.action = CONTROL_EVENT_COMMAND_COLLAPSE_NOTIFICATION_PANEL; + + if (!controller_push_event(controller, &control_event)) { + LOGW("Cannot collapse notification panel"); + } +} + static void switch_fps_counter_state(struct frames *frames) { mutex_lock(frames->mutex); if (frames->fps_counter.started) { @@ -162,47 +182,42 @@ void input_manager_process_key(struct input_manager *input_manager, // capture all Ctrl events if (ctrl | meta) { - SDL_bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT); - if (shift) { - // currently, there is no shortcut involving SHIFT - return; - } - SDL_Keycode keycode = event->keysym.sym; int action = event->type == SDL_KEYDOWN ? ACTION_DOWN : ACTION_UP; SDL_bool repeat = event->repeat; + SDL_bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT); switch (keycode) { case SDLK_h: - if (ctrl && !meta && !repeat) { + if (ctrl && !meta && !shift && !repeat) { action_home(input_manager->controller, action); } return; case SDLK_b: // fall-through case SDLK_BACKSPACE: - if (ctrl && !meta && !repeat) { + if (ctrl && !meta && !shift && !repeat) { action_back(input_manager->controller, action); } return; case SDLK_s: - if (ctrl && !meta && !repeat) { + if (ctrl && !meta && !shift && !repeat) { action_app_switch(input_manager->controller, action); } return; case SDLK_m: - if (ctrl && !meta && !repeat) { + if (ctrl && !meta && !shift && !repeat) { action_menu(input_manager->controller, action); } return; case SDLK_p: - if (ctrl && !meta && !repeat) { + if (ctrl && !meta && !shift && !repeat) { action_power(input_manager->controller, action); } return; case SDLK_DOWN: #ifdef __APPLE__ - if (!ctrl && meta) { + if (!ctrl && meta && !shift) { #else - if (ctrl && !meta) { + if (ctrl && !meta && !shift) { #endif // forward repeated events action_volume_down(input_manager->controller, action); @@ -210,39 +225,53 @@ void input_manager_process_key(struct input_manager *input_manager, return; case SDLK_UP: #ifdef __APPLE__ - if (!ctrl && meta) { + if (!ctrl && meta && !shift) { #else - if (ctrl && !meta) { + if (ctrl && !meta && !shift) { #endif // forward repeated events action_volume_up(input_manager->controller, action); } return; case SDLK_v: - if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) { + if (ctrl && !meta && !shift && !repeat + && event->type == SDL_KEYDOWN) { clipboard_paste(input_manager->controller); } return; case SDLK_f: - if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) { + if (ctrl && !meta && !shift && !repeat + && event->type == SDL_KEYDOWN) { screen_switch_fullscreen(input_manager->screen); } return; case SDLK_x: - if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) { + if (ctrl && !meta && !shift && !repeat + && event->type == SDL_KEYDOWN) { screen_resize_to_fit(input_manager->screen); } return; case SDLK_g: - if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) { + if (ctrl && !meta && !shift && !repeat + && event->type == SDL_KEYDOWN) { screen_resize_to_pixel_perfect(input_manager->screen); } return; case SDLK_i: - if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) { + if (ctrl && !meta && !shift && !repeat + && event->type == SDL_KEYDOWN) { switch_fps_counter_state(input_manager->frames); } return; + case SDLK_n: + if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) { + if (shift) { + collapse_notification_panel(input_manager->controller); + } else { + expand_notification_panel(input_manager->controller); + } + } + return; } return; diff --git a/app/src/main.c b/app/src/main.c index 35bb74ea..f18b3aaf 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -120,6 +120,12 @@ static void usage(const char *arg0) { " Right-click (when screen is off)\n" " turn screen on\n" "\n" + " Ctrl+n\n" + " expand notification panel\n" + "\n" + " Ctrl+Shift+n\n" + " collapse notification panel\n" + "\n" " Ctrl+v\n" " paste computer clipboard to device\n" "\n" diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlEvent.java b/server/src/main/java/com/genymobile/scrcpy/ControlEvent.java index 3c9cbda1..8b0f9e2b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlEvent.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlEvent.java @@ -12,6 +12,8 @@ public final class ControlEvent { public static final int TYPE_COMMAND = 4; public static final int COMMAND_BACK_OR_SCREEN_ON = 0; + public static final int COMMAND_EXPAND_NOTIFICATION_PANEL = 1; + public static final int COMMAND_COLLAPSE_NOTIFICATION_PANEL = 2; private int type; private String text; diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index d2862acb..60122c5a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -132,6 +132,14 @@ public final class Device { this.rotationListener = rotationListener; } + public void expandNotificationPanel() { + serviceManager.getStatusBarManager().expandNotificationsPanel(); + } + + public void collapsePanels() { + serviceManager.getStatusBarManager().collapsePanels(); + } + static Rect flipRect(Rect crop) { return new Rect(crop.top, crop.left, crop.bottom, crop.right); } diff --git a/server/src/main/java/com/genymobile/scrcpy/EventController.java b/server/src/main/java/com/genymobile/scrcpy/EventController.java index 547e20ca..5fa2cab2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/EventController.java +++ b/server/src/main/java/com/genymobile/scrcpy/EventController.java @@ -1,6 +1,7 @@ package com.genymobile.scrcpy; import com.genymobile.scrcpy.wrappers.InputManager; +import com.genymobile.scrcpy.wrappers.ServiceManager; import android.graphics.Point; import android.os.SystemClock; @@ -172,6 +173,12 @@ public class EventController { switch (action) { case ControlEvent.COMMAND_BACK_OR_SCREEN_ON: return pressBackOrTurnScreenOn(); + case ControlEvent.COMMAND_EXPAND_NOTIFICATION_PANEL: + device.expandNotificationPanel(); + return true; + case ControlEvent.COMMAND_COLLAPSE_NOTIFICATION_PANEL: + device.collapsePanels(); + return true; default: Ln.w("Unsupported command: " + action); } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java index 2d98d0a8..3bcdc0e6 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java @@ -14,6 +14,7 @@ public final class ServiceManager { private DisplayManager displayManager; private InputManager inputManager; private PowerManager powerManager; + private StatusBarManager statusBarManager; public ServiceManager() { try { @@ -60,4 +61,11 @@ public final class ServiceManager { } return powerManager; } + + public StatusBarManager getStatusBarManager() { + if (statusBarManager == null) { + statusBarManager = new StatusBarManager(getService("statusbar", "com.android.internal.statusbar.IStatusBarService")); + } + return statusBarManager; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java new file mode 100644 index 00000000..aacd5f1a --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/StatusBarManager.java @@ -0,0 +1,41 @@ +package com.genymobile.scrcpy.wrappers; + +import android.annotation.SuppressLint; +import android.os.IInterface; +import android.view.InputEvent; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class StatusBarManager { + + private final IInterface manager; + private final Method expandNotificationsPanelMethod; + private final Method collapsePanelsMethod; + + public StatusBarManager(IInterface manager) { + this.manager = manager; + try { + expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel"); + collapsePanelsMethod = manager.getClass().getMethod("collapsePanels"); + } catch (NoSuchMethodException e) { + throw new AssertionError(e); + } + } + + public void expandNotificationsPanel() { + try { + expandNotificationsPanelMethod.invoke(manager); + } catch (InvocationTargetException | IllegalAccessException e) { + throw new AssertionError(e); + } + } + + public void collapsePanels() { + try { + collapsePanelsMethod.invoke(manager); + } catch (InvocationTargetException | IllegalAccessException e) { + throw new AssertionError(e); + } + } +}