Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added shortcut to copy formatted notification text via xclip #689

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions config.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ struct settings defaults = {
.code = 0,.sym = NoSymbol,.is_valid = false
}, /* ignore this */

.copy_ks = {.str = "none",
.code = 0,.sym = NoSymbol,.is_valid = false
}, /* ignore this */

.mouse_left_click = MOUSE_CLOSE_CURRENT,

.mouse_middle_click = MOUSE_DO_ACTION,
Expand Down
120 changes: 120 additions & 0 deletions src/clipboard.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/* copyright 2020 Sascha Kruse and contributors (see LICENSE for licensing information) */

#include "clipboard.h"

#include <errno.h>
#include <glib.h>

#include "log.h"
#include "notification.h"
#include "queues.h"
#include "settings.h"
#include "utils.h"

struct notification_lock {
struct notification *n;
gint64 timeout;
};
static gpointer copy_alert_thread(gpointer data);

/** Call xclip with the specified input. Blocks until xclip is finished.
*
* @param xclip_input The data to be copied by xclip
*/
void invoke_xclip(const char *xclip_input)
{
if (!settings.xclip_cmd) {
LOG_C("Unable to open xclip: No xclip command set.");
return;
}

ASSERT_OR_RET(STR_FULL(xclip_input),);

gint dunst_to_xclip;
GError *err = NULL;

g_spawn_async_with_pipes(NULL,
settings.xclip_cmd,
NULL,
G_SPAWN_DEFAULT
| G_SPAWN_SEARCH_PATH,
NULL,
NULL,
NULL,
&dunst_to_xclip,
NULL,
NULL,
&err);

if (err) {
LOG_C("Cannot spawn xclip: %s", err->message);
g_error_free(err);
} else {
size_t wlen = strlen(xclip_input);
if (write(dunst_to_xclip, xclip_input, wlen) != wlen) {
LOG_W("Cannot feed xclip with input: %s", strerror(errno));
}
close(dunst_to_xclip);
}
}

void copy_alert_contents()
{
GError *err = NULL;
g_thread_unref(g_thread_try_new("xclip",
copy_alert_thread,
NULL,
&err));

if (err) {
LOG_C("Cannot start thread to call xclip: %s", err->message);
g_error_free(err);
}
}

static gpointer copy_alert_thread(gpointer data)
{
char *xclip_input = NULL;
GList *locked_notifications = NULL;

for (const GList *iter = queues_get_displayed(); iter;
iter = iter->next) {
struct notification *n = iter->data;


// Reference and lock the notification if we need it
notification_ref(n);

struct notification_lock *nl =
g_malloc(sizeof(struct notification_lock));

nl->n = n;
nl->timeout = n->timeout;
n->timeout = 0;

locked_notifications = g_list_prepend(locked_notifications, nl);

xclip_input = string_append(xclip_input, n->clipboard_msg, "\n");
}

invoke_xclip(xclip_input);
g_free(xclip_input);

// unref all notifications
for (GList *iter = locked_notifications;
iter;
iter = iter->next) {

struct notification_lock *nl = iter->data;
struct notification *n = nl->n;

n->timeout = nl->timeout;

g_free(nl);
notification_unref(n);
}
g_list_free(locked_notifications);

return NULL;
}
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
8 changes: 8 additions & 0 deletions src/clipboard.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/* copyright 2020 Sascha Kruse and contributors (see LICENSE for licensing information) */
#ifndef DUNST_CLIPBOARD_H
#define DUNST_CLIPBOARD_H

void copy_alert_contents();

#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
106 changes: 106 additions & 0 deletions src/notification.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

static void notification_extract_urls(struct notification *n);
static void notification_format_message(struct notification *n);
static void notification_format_clipboard_message(struct notification *n);

/* see notification.h */
const char *enum_to_string_fullscreen(enum behavior_fullscreen in)
Expand Down Expand Up @@ -308,6 +309,7 @@ struct notification *notification_create(void)
n->first_render = true;
n->markup = settings.markup;
n->format = settings.format;
n->clipboard_format = settings.clipboard_format;

n->timestamp = time_monotonic_now();

Expand Down Expand Up @@ -389,6 +391,110 @@ void notification_init(struct notification *n)
/* UPDATE derived fields */
notification_extract_urls(n);
notification_format_message(n);
notification_format_clipboard_message(n);
}

static void notification_format_clipboard_message(struct notification *n)
{
g_clear_pointer(&n->clipboard_msg, g_free);

n->clipboard_msg = string_replace_all("\\n", "\n", g_strdup(n->clipboard_format));

/* replace all formatter */
for(char *substr = strchr(n->clipboard_msg, '%');
substr && *substr;
substr = strchr(substr, '%')) {

char pg[16];
char *icon_tmp;

switch(substr[1]) {
case 'a':
notification_replace_single_field(
&n->clipboard_msg,
&substr,
n->appname,
MARKUP_NO);
break;
case 's':
notification_replace_single_field(
&n->clipboard_msg,
&substr,
n->summary,
MARKUP_NO);
break;
case 'b':
notification_replace_single_field(
&n->clipboard_msg,
&substr,
n->body,
n->markup);
break;
case 'I':
icon_tmp = g_strdup(n->iconname);
notification_replace_single_field(
&n->clipboard_msg,
&substr,
icon_tmp ? basename(icon_tmp) : "",
MARKUP_NO);
g_free(icon_tmp);
break;
case 'i':
notification_replace_single_field(
&n->clipboard_msg,
&substr,
n->iconname ? n->iconname : "",
MARKUP_NO);
break;
case 'p':
if (n->progress != -1)
sprintf(pg, "[%3d%%]", n->progress);

notification_replace_single_field(
&n->clipboard_msg,
&substr,
n->progress != -1 ? pg : "",
MARKUP_NO);
break;
case 'n':
if (n->progress != -1)
sprintf(pg, "%d", n->progress);

notification_replace_single_field(
&n->clipboard_msg,
&substr,
n->progress != -1 ? pg : "",
MARKUP_NO);
break;
case '%':
notification_replace_single_field(
&n->clipboard_msg,
&substr,
"%",
MARKUP_NO);
break;
case '\0':
LOG_W("format_string has trailing %% character. "
"To escape it use %%%%.");
substr++;
break;
default:
LOG_W("format_string %%%c is unknown.", substr[1]);
// shift substr pointer forward,
// as we can't interpret the format string
substr++;
break;
}
}

n->clipboard_msg = g_strchomp(n->clipboard_msg);

/* truncate overlong messages */
if (strnlen(n->clipboard_msg, DUNST_NOTIF_MAX_CHARS + 1) > DUNST_NOTIF_MAX_CHARS) {
char * buffer = g_strndup(n->clipboard_msg, DUNST_NOTIF_MAX_CHARS);
g_free(n->clipboard_msg);
n->clipboard_msg = buffer;
}
}

static void notification_format_message(struct notification *n)
Expand Down
2 changes: 2 additions & 0 deletions src/notification.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ struct notification {

enum markup_mode markup;
const char *format;
const char *clipboard_format;
const char *script;
struct notification_colors colors;

Expand All @@ -83,6 +84,7 @@ struct notification {

/* derived fields */
char *msg; /**< formatted message */
char *clipboard_msg; /**< formatted message (in the manner the user prefers for the clipboard) */
char *text_to_render; /**< formatted message (with age and action indicators) */
char *urls; /**< urllist delimited by '\\n' */
};
Expand Down
2 changes: 2 additions & 0 deletions src/rules.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ void rule_apply(struct rule *r, struct notification *n)
}
if (r->format)
n->format = r->format;
if (r->clipboard_format)
n->clipboard_format = r->clipboard_format;
if (r->script)
n->script = r->script;
if (r->set_stack_tag) {
Expand Down
1 change: 1 addition & 0 deletions src/rules.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ struct rule {
char *bg;
char *fc;
const char *format;
const char *clipboard_format;
const char *script;
enum behavior_fullscreen fullscreen;
char *set_stack_tag;
Expand Down
28 changes: 28 additions & 0 deletions src/settings.c
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,12 @@ void load_settings(char *cmdline_config_path)
"The format template for the notifications"
);

settings.clipboard_format = option_get_string(
"global",
"clipboard_format", "-clipboard_format", defaults.clipboard_format,
"The format template for how the notification will look if copied to the clipboard"
);

settings.sort = option_get_bool(
"global",
"sort", "-sort", defaults.sort,
Expand Down Expand Up @@ -391,6 +397,21 @@ void load_settings(char *cmdline_config_path)
}
}

settings.xclip = option_get_path(
"global",
"xclip", "-xclip", defaults.xclip,
"path to xclip"
);

{
GError *error = NULL;
if (!g_shell_parse_argv(settings.xclip, NULL, &settings.xclip_cmd, &error)) {
LOG_W("Unable to parse xclip command: '%s'."
"xclip functionality will be disabled.", error->message);
g_error_free(error);
settings.xclip_cmd = NULL;
}
}

settings.browser = option_get_path(
"global",
Expand Down Expand Up @@ -687,6 +708,12 @@ void load_settings(char *cmdline_config_path)
"Shortcut for context menu"
);

settings.copy_ks.str = option_get_string(
"shortcuts",
"copy", "-copy_key", defaults.copy_ks.str,
"Shortcut to copy the contents of the notification to the clipboard"
);

settings.print_notifications = cmdline_get_bool(
"-print", false,
"Print notifications to cmdline (DEBUG)"
Expand Down Expand Up @@ -759,6 +786,7 @@ void load_settings(char *cmdline_config_path)
r->bg = ini_get_string(cur_section, "background", r->bg);
r->fc = ini_get_string(cur_section, "frame_color", r->fc);
r->format = ini_get_string(cur_section, "format", r->format);
r->clipboard_format = ini_get_string(cur_section, "clipboard_format", r->clipboard_format);
r->new_icon = ini_get_string(cur_section, "new_icon", r->new_icon);
r->history_ignore = ini_get_bool(cur_section, "history_ignore", r->history_ignore);
r->match_transient = ini_get_bool(cur_section, "match_transient", r->match_transient);
Expand Down
4 changes: 4 additions & 0 deletions src/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ struct settings {
struct notification_colors colors_norm;
struct notification_colors colors_crit;
char *format;
char *clipboard_format;
gint64 timeouts[3];
char *icons[3];
unsigned int transparency;
Expand Down Expand Up @@ -75,6 +76,8 @@ struct settings {
char **dmenu_cmd;
char *browser;
char **browser_cmd;
char *xclip;
char **xclip_cmd;
enum icon_position icon_position;
enum vertical_alignment vertical_alignment;
int min_icon_size;
Expand All @@ -86,6 +89,7 @@ struct settings {
struct keyboard_shortcut close_all_ks;
struct keyboard_shortcut history_ks;
struct keyboard_shortcut context_ks;
struct keyboard_shortcut copy_ks;
bool force_xinerama;
int corner_radius;
enum mouse_action mouse_left_click;
Expand Down
Loading