diff options
Diffstat (limited to 'src/androidselect.c')
| -rw-r--r-- | src/androidselect.c | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/src/androidselect.c b/src/androidselect.c index 9910e7921de..5551598032d 100644 --- a/src/androidselect.c +++ b/src/androidselect.c | |||
| @@ -22,6 +22,9 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ | |||
| 22 | #include <minmax.h> | 22 | #include <minmax.h> |
| 23 | #include <unistd.h> | 23 | #include <unistd.h> |
| 24 | 24 | ||
| 25 | #include <boot-time.h> | ||
| 26 | #include <sys/types.h> | ||
| 27 | |||
| 25 | #include "lisp.h" | 28 | #include "lisp.h" |
| 26 | #include "blockinput.h" | 29 | #include "blockinput.h" |
| 27 | #include "coding.h" | 30 | #include "coding.h" |
| @@ -466,6 +469,232 @@ does not have any corresponding data. In that case, use | |||
| 466 | 469 | ||
| 467 | 470 | ||
| 468 | 471 | ||
| 472 | /* Desktop notifications. `android-desktop-notify' implements a | ||
| 473 | facsimile of `notifications-notify'. */ | ||
| 474 | |||
| 475 | /* Structure describing the EmacsDesktopNotification class. */ | ||
| 476 | |||
| 477 | struct android_emacs_desktop_notification | ||
| 478 | { | ||
| 479 | jclass class; | ||
| 480 | jmethodID init; | ||
| 481 | jmethodID display; | ||
| 482 | }; | ||
| 483 | |||
| 484 | /* Methods provided by the EmacsDesktopNotification class. */ | ||
| 485 | static struct android_emacs_desktop_notification notification_class; | ||
| 486 | |||
| 487 | /* Initialize virtual function IDs and class pointers tied to the | ||
| 488 | EmacsDesktopNotification class. */ | ||
| 489 | |||
| 490 | static void | ||
| 491 | android_init_emacs_desktop_notification (void) | ||
| 492 | { | ||
| 493 | jclass old; | ||
| 494 | |||
| 495 | notification_class.class | ||
| 496 | = (*android_java_env)->FindClass (android_java_env, | ||
| 497 | "org/gnu/emacs/EmacsDesktopNotification"); | ||
| 498 | eassert (notification_class.class); | ||
| 499 | |||
| 500 | old = notification_class.class; | ||
| 501 | notification_class.class | ||
| 502 | = (jclass) (*android_java_env)->NewGlobalRef (android_java_env, | ||
| 503 | old); | ||
| 504 | ANDROID_DELETE_LOCAL_REF (old); | ||
| 505 | |||
| 506 | if (!notification_class.class) | ||
| 507 | emacs_abort (); | ||
| 508 | |||
| 509 | #define FIND_METHOD(c_name, name, signature) \ | ||
| 510 | notification_class.c_name \ | ||
| 511 | = (*android_java_env)->GetMethodID (android_java_env, \ | ||
| 512 | notification_class.class, \ | ||
| 513 | name, signature); \ | ||
| 514 | assert (notification_class.c_name); | ||
| 515 | |||
| 516 | FIND_METHOD (init, "<init>", "(Ljava/lang/String;" | ||
| 517 | "Ljava/lang/String;Ljava/lang/String;" | ||
| 518 | "Ljava/lang/String;I)V"); | ||
| 519 | FIND_METHOD (display, "display", "()V"); | ||
| 520 | #undef FIND_METHOD | ||
| 521 | } | ||
| 522 | |||
| 523 | /* Display a desktop notification with the provided TITLE, BODY, | ||
| 524 | REPLACES_ID, GROUP and URGENCY. Return an identifier for the | ||
| 525 | resulting notification. */ | ||
| 526 | |||
| 527 | static intmax_t | ||
| 528 | android_notifications_notify_1 (Lisp_Object title, Lisp_Object body, | ||
| 529 | Lisp_Object replaces_id, | ||
| 530 | Lisp_Object group, Lisp_Object urgency) | ||
| 531 | { | ||
| 532 | static intmax_t counter; | ||
| 533 | intmax_t id; | ||
| 534 | jstring title1, body1, group1, identifier1; | ||
| 535 | jint type; | ||
| 536 | jobject notification; | ||
| 537 | char identifier[INT_STRLEN_BOUND (int) | ||
| 538 | + INT_STRLEN_BOUND (long int) | ||
| 539 | + INT_STRLEN_BOUND (intmax_t) | ||
| 540 | + sizeof "..."]; | ||
| 541 | struct timespec boot_time; | ||
| 542 | |||
| 543 | if (EQ (urgency, Qlow)) | ||
| 544 | type = 2; /* IMPORTANCE_LOW */ | ||
| 545 | else if (EQ (urgency, Qnormal)) | ||
| 546 | type = 3; /* IMPORTANCE_DEFAULT */ | ||
| 547 | else if (EQ (urgency, Qcritical)) | ||
| 548 | type = 4; /* IMPORTANCE_HIGH */ | ||
| 549 | else | ||
| 550 | signal_error ("Invalid notification importance given", urgency); | ||
| 551 | |||
| 552 | if (NILP (replaces_id)) | ||
| 553 | { | ||
| 554 | /* Generate a new identifier. */ | ||
| 555 | INT_ADD_WRAPV (counter, 1, &counter); | ||
| 556 | id = counter; | ||
| 557 | } | ||
| 558 | else | ||
| 559 | { | ||
| 560 | CHECK_INTEGER (replaces_id); | ||
| 561 | if (!integer_to_intmax (replaces_id, &id)) | ||
| 562 | id = -1; /* Overflow. */ | ||
| 563 | } | ||
| 564 | |||
| 565 | /* Generate a unique identifier for this notification. Because | ||
| 566 | Android persists notifications past system shutdown, also include | ||
| 567 | the boot time within IDENTIFIER. Scale it down to avoid being | ||
| 568 | perturbed by minor instabilities in the returned boot time, | ||
| 569 | however. */ | ||
| 570 | |||
| 571 | boot_time.tv_sec = 0; | ||
| 572 | get_boot_time (&boot_time); | ||
| 573 | sprintf (identifier, "%d.%ld.%jd", (int) getpid (), | ||
| 574 | (long int) (boot_time.tv_sec / 2), id); | ||
| 575 | |||
| 576 | /* Encode all strings into their Java counterparts. */ | ||
| 577 | title1 = android_build_string (title); | ||
| 578 | body1 = android_build_string (body); | ||
| 579 | group1 = android_build_string (group); | ||
| 580 | identifier1 = android_build_jstring (identifier); | ||
| 581 | |||
| 582 | /* Create the notification. */ | ||
| 583 | notification | ||
| 584 | = (*android_java_env)->NewObject (android_java_env, | ||
| 585 | notification_class.class, | ||
| 586 | notification_class.init, | ||
| 587 | title1, body1, group1, | ||
| 588 | identifier1, type); | ||
| 589 | android_exception_check_4 (title1, body1, group1, identifier1); | ||
| 590 | |||
| 591 | /* Delete unused local references. */ | ||
| 592 | ANDROID_DELETE_LOCAL_REF (title1); | ||
| 593 | ANDROID_DELETE_LOCAL_REF (body1); | ||
| 594 | ANDROID_DELETE_LOCAL_REF (group1); | ||
| 595 | ANDROID_DELETE_LOCAL_REF (identifier1); | ||
| 596 | |||
| 597 | /* Display the notification. */ | ||
| 598 | (*android_java_env)->CallNonvirtualVoidMethod (android_java_env, | ||
| 599 | notification, | ||
| 600 | notification_class.class, | ||
| 601 | notification_class.display); | ||
| 602 | android_exception_check_1 (notification); | ||
| 603 | ANDROID_DELETE_LOCAL_REF (notification); | ||
| 604 | |||
| 605 | /* Return the ID. */ | ||
| 606 | return id; | ||
| 607 | } | ||
| 608 | |||
| 609 | DEFUN ("android-notifications-notify", Fandroid_notifications_notify, | ||
| 610 | Sandroid_notifications_notify, 0, MANY, 0, doc: | ||
| 611 | /* Display a desktop notification. | ||
| 612 | ARGS must contain keywords followed by values. Each of the following | ||
| 613 | keywords is understood: | ||
| 614 | |||
| 615 | :title The notification title. | ||
| 616 | :body The notification body. | ||
| 617 | :replaces-id The ID of a previous notification to supersede. | ||
| 618 | :group The notification group, or nil. | ||
| 619 | :urgency One of the symbols `low', `normal' or `critical', | ||
| 620 | defining the importance of the notification group. | ||
| 621 | |||
| 622 | The notification group and urgency are ignored on Android 7.1 and | ||
| 623 | earlier versions of Android. Outside such older systems, it | ||
| 624 | identifies a category that will be displayed in the system Settings | ||
| 625 | menu. The urgency provided always extends to affect all notifications | ||
| 626 | displayed within that category. If the group is not provided, it | ||
| 627 | defaults to the string "Desktop Notifications". | ||
| 628 | |||
| 629 | When the system is running Android 13 or later, notifications sent | ||
| 630 | will be silently disregarded unless permission to display | ||
| 631 | notifications is expressly granted from the "App Info" settings panel | ||
| 632 | corresponding to Emacs. | ||
| 633 | |||
| 634 | A title and body must be supplied. Value is an integer (fixnum or | ||
| 635 | bignum) uniquely designating the notification displayed, which may | ||
| 636 | subsequently be specified as the `:replaces-id' of another call to | ||
| 637 | this function. | ||
| 638 | |||
| 639 | usage: (android-notifications-notify &rest ARGS) */) | ||
| 640 | (ptrdiff_t nargs, Lisp_Object *args) | ||
| 641 | { | ||
| 642 | Lisp_Object title, body, replaces_id, group, urgency; | ||
| 643 | Lisp_Object key, value; | ||
| 644 | ptrdiff_t i; | ||
| 645 | |||
| 646 | if (!android_init_gui) | ||
| 647 | error ("No Android display connection!"); | ||
| 648 | |||
| 649 | /* Clear each variable above. */ | ||
| 650 | title = body = replaces_id = group = urgency = Qnil; | ||
| 651 | |||
| 652 | /* If NARGS is odd, error. */ | ||
| 653 | |||
| 654 | if (nargs & 1) | ||
| 655 | error ("Odd number of arguments in call to `android-notifications-notify'"); | ||
| 656 | |||
| 657 | /* Next, iterate through ARGS, searching for arguments. */ | ||
| 658 | |||
| 659 | for (i = 0; i < nargs; i += 2) | ||
| 660 | { | ||
| 661 | key = args[i]; | ||
| 662 | value = args[i + 1]; | ||
| 663 | |||
| 664 | if (EQ (key, QCtitle)) | ||
| 665 | title = value; | ||
| 666 | else if (EQ (key, QCbody)) | ||
| 667 | body = value; | ||
| 668 | else if (EQ (key, QCreplaces_id)) | ||
| 669 | replaces_id = value; | ||
| 670 | else if (EQ (key, QCgroup)) | ||
| 671 | group = value; | ||
| 672 | else if (EQ (key, QCurgency)) | ||
| 673 | urgency = value; | ||
| 674 | } | ||
| 675 | |||
| 676 | /* Demand at least TITLE and BODY be present. */ | ||
| 677 | |||
| 678 | if (NILP (title) || NILP (body)) | ||
| 679 | error ("Title or body not provided"); | ||
| 680 | |||
| 681 | /* Now check the type and possibly expand each non-nil argument. */ | ||
| 682 | |||
| 683 | CHECK_STRING (title); | ||
| 684 | CHECK_STRING (body); | ||
| 685 | |||
| 686 | if (NILP (urgency)) | ||
| 687 | urgency = Qlow; | ||
| 688 | |||
| 689 | if (NILP (group)) | ||
| 690 | group = build_string ("Desktop Notifications"); | ||
| 691 | |||
| 692 | return make_int (android_notifications_notify_1 (title, body, replaces_id, | ||
| 693 | group, urgency)); | ||
| 694 | } | ||
| 695 | |||
| 696 | |||
| 697 | |||
| 469 | void | 698 | void |
| 470 | init_androidselect (void) | 699 | init_androidselect (void) |
| 471 | { | 700 | { |
| @@ -476,6 +705,7 @@ init_androidselect (void) | |||
| 476 | return; | 705 | return; |
| 477 | 706 | ||
| 478 | android_init_emacs_clipboard (); | 707 | android_init_emacs_clipboard (); |
| 708 | android_init_emacs_desktop_notification (); | ||
| 479 | 709 | ||
| 480 | make_clipboard = clipboard_class.make_clipboard; | 710 | make_clipboard = clipboard_class.make_clipboard; |
| 481 | tem | 711 | tem |
| @@ -496,6 +726,16 @@ init_androidselect (void) | |||
| 496 | void | 726 | void |
| 497 | syms_of_androidselect (void) | 727 | syms_of_androidselect (void) |
| 498 | { | 728 | { |
| 729 | DEFSYM (QCtitle, ":title"); | ||
| 730 | DEFSYM (QCbody, ":body"); | ||
| 731 | DEFSYM (QCreplaces_id, ":replaces-id"); | ||
| 732 | DEFSYM (QCgroup, ":group"); | ||
| 733 | DEFSYM (QCurgency, ":urgency"); | ||
| 734 | |||
| 735 | DEFSYM (Qlow, "low"); | ||
| 736 | DEFSYM (Qnormal, "normal"); | ||
| 737 | DEFSYM (Qcritical, "critical"); | ||
| 738 | |||
| 499 | defsubr (&Sandroid_clipboard_owner_p); | 739 | defsubr (&Sandroid_clipboard_owner_p); |
| 500 | defsubr (&Sandroid_set_clipboard); | 740 | defsubr (&Sandroid_set_clipboard); |
| 501 | defsubr (&Sandroid_get_clipboard); | 741 | defsubr (&Sandroid_get_clipboard); |
| @@ -503,4 +743,6 @@ syms_of_androidselect (void) | |||
| 503 | defsubr (&Sandroid_browse_url); | 743 | defsubr (&Sandroid_browse_url); |
| 504 | defsubr (&Sandroid_get_clipboard_targets); | 744 | defsubr (&Sandroid_get_clipboard_targets); |
| 505 | defsubr (&Sandroid_get_clipboard_data); | 745 | defsubr (&Sandroid_get_clipboard_data); |
| 746 | |||
| 747 | defsubr (&Sandroid_notifications_notify); | ||
| 506 | } | 748 | } |