-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
indicator-emojitwo
executable file
·556 lines (478 loc) · 23.5 KB
/
indicator-emojitwo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
import gettext
import json
import logging
import os
import re
import setproctitle
import signal
import sys
import time
from collections import OrderedDict
from gi import require_version
from os.path import expanduser
require_version('AyatanaAppIndicator3', '0.1')
require_version('Gtk', '3.0')
require_version('Notify', '0.7')
from gi.repository import GLib, Gtk, GObject, Gdk, Notify, GdkPixbuf
from gi.repository import AyatanaAppIndicator3 as appindicator
# Workaround introspection bug, gnome bug 622084
signal.signal(signal.SIGINT, signal.SIG_DFL)
__VERSION__ = '19.10.1'
# i18n
gettext.install('indicator-emojitwo', os.path.join('/', 'usr', 'share', 'locale'))
# Where is the data?
directories = [expanduser("~") + "/.local/share/indicator-emojitwo",
"/usr/local/share/indicator-emojitwo",
"/usr/share/indicator-emojitwo",
os.path.dirname(os.path.realpath(__file__))]
for d in directories:
if os.path.isdir(d):
directory = d
break
# Categories definitions
categories = ["recent", "people", "food", "nature", "objects", "activity", "travel", "flags", "symbols"]
# Settings handling
default_settings = settings = {
"toned": -1,
"notifications": False,
"lowend": False,
"recent": 20
}
configpath = expanduser("~") + "/.config/indicator-emojitwo"
os.path.isdir(configpath) or os.mkdir(configpath)
builder = Gtk.Builder()
def terminate(self, _=None):
Gtk.main_quit()
def open_settings_window(self, _):
global settings
builder.add_from_file(directory + "/assets/settings.glade")
builder.connect_signals(SettingsButtonHandler())
settings_window = builder.get_object("settings_window")
settings_window.show_all()
settings_window.present()
settings_window.grab_focus()
# Apply settings
builder.get_object("combo_toned").set_active(settings["toned"]+1)
builder.get_object("check_notifications").set_active(settings["notifications"])
builder.get_object("check_lowend").set_active(settings["lowend"])
builder.get_object("recent").set_value(settings["recent"])
class SettingsButtonHandler:
def onButtonPressed(self, button):
apply_settings()
button.get_parent_window().destroy()
def onToggled(self, button):
pass
def apply_settings():
global settings
# Get settings from dialog
settings = {
"toned": builder.get_object("combo_toned").get_active()-1,
"notifications": builder.get_object("check_notifications").get_active(),
"lowend": builder.get_object("check_lowend").get_active(),
"recent": builder.get_object("recent").get_value_as_int()
}
# Save settings to file
save_settings()
# Reload app to apply new settings - FIXME: This is ugly and slow, could be much better
os.execv(__file__, sys.argv)
def save_settings():
global settings
with open(configfile, 'w') as outfile:
json.dump(settings, outfile)
# Search feature
searchbuilder = None
search_window = None
def open_search_window(self, w):
global searchbuilder
global search_window
# Build window only once
try:
search_window.show_all()
search_window.present()
search_window.grab_focus()
except:
# Build window
searchbuilder = Gtk.Builder()
searchbuilder.add_from_file(directory + "/assets/chooser.glade")
searchbuilder.connect_signals(SearchHandler())
search_window = searchbuilder.get_object("search_window")
search_window.show_all()
search_window.present()
search_window.grab_focus()
# Put recent icons by default
iconstore = searchbuilder.get_object("iconstore")
global sorted_recent, searchresults
for i in sorted_recent:
emoji_name = sorted_recent[i]["name"]
emoji_image = GdkPixbuf.Pixbuf.new_from_file_at_size(directory + "/assets/svg/" + sorted_recent[i]["unicode"] + ".svg", 24, 24)
emoji_code = sorted_recent[i]["unicode"]
iconstore.append([emoji_image, emoji_code, emoji_name])
searchresults = []
for i in sorted_recent.keys():
searchresults.append(sorted_recent[i])
return
searchresults = None
selectionChanged = False
class SearchHandler:
def onSearchChanged(self, search):
global searchresults
search = searchbuilder.get_object("search").get_text()
iconstore = searchbuilder.get_object("iconstore")
iconstore.clear()
if search == "":
# No search? Put recent icons
global sorted_recent
for i in sorted_recent:
emoji_name = sorted_recent[i]["name"]
emoji_image = GdkPixbuf.Pixbuf.new_from_file_at_size(directory + "/assets/svg/" + sorted_recent[i]["unicode"] + ".svg", 24, 24)
emoji_code = sorted_recent[i]["unicode"]
iconstore.append([emoji_image, emoji_code, emoji_name])
searchresults = []
for i in sorted_recent.keys():
searchresults.append(sorted_recent[i])
return
numfound=0
searchresults = []
for i in sorted_data:
match = False
for keyword in sorted_data[i]["keywords"]:
if keyword.find(search)>-1:
match = True
if sorted_data[i]["name"].find(search)>-1:
match = True
if match == True:
numfound += 1
if numfound > 50:
return
searchresults.append(sorted_data[i])
emoji_name = sorted_data[i]["name"]
emoji_image = GdkPixbuf.Pixbuf.new_from_file_at_size(directory + "/assets/svg/" + sorted_data[i]["unicode"] + ".svg", 24, 24)
emoji_code = sorted_data[i]["unicode"]
iconstore.append([emoji_image, emoji_code, emoji_name])
def onIconActivated(self, icon, index):
global searchresults
item_response(self, searchresults[int(index.to_string())])
icon.get_parent_window().hide()
def onSelectionChanged(self, data):
global selectionChanged
selectionChanged = True
def onKeyReleased(self, window, event):
global selectionChanged
if event.keyval == Gdk.KEY_Escape:
window.hide()
elif event.keyval == Gdk.KEY_Down:
searchbuilder.get_object("iconview").grab_focus()
elif event.keyval == Gdk.KEY_Up and selectionChanged == False:
searchbuilder.get_object("search").grab_focus()
selectionChanged = False
def onOutFocus(self, window, data):
window.hide()
# Load settings at startup
configfile = configpath + "/settings.json"
if os.path.isfile(configfile):
with open(configfile) as settings_json_file:
try:
settings = json.load(settings_json_file)
except ValueError:
logging.exception("Could not load JSON settings file")
save_settings()
else:
save_settings()
for key in default_settings:
if not key in settings:
settings[key] = default_settings[key]
# Load recent emojis at startup
recentfile = configpath + "/recent.json"
if os.path.isfile(recentfile):
with open(recentfile) as recent_json_file:
recent = json.load(recent_json_file)
recentindex = 0
for k in recent.keys():
if int(recent[k]["recent_order"]) > recentindex:
recentindex = int(recent[k]["recent_order"])
recentindex += 1
else:
recent = dict()
recentindex = 0
# If using lowend, load lowend information
if settings["lowend"]:
lowendfile = directory + "/assets/lowend.json"
if os.path.isfile(lowendfile):
with open(lowendfile) as lowend_json_file:
lowend = json.load(lowend_json_file)
# Refresh recent icons submenu
sorted_recent = None
def refresh_recent_submenu():
global recent, recent_items, sorted_recent
# Rearrange data
def orderfunc(tup):
key, d = tup
return -int(d["recent_order"])
sorted_recent = sorted(recent.items(), key=orderfunc)
sorted_recent = OrderedDict(sorted_recent)
# Refresh icons
i = 0
for key in sorted_recent:
if i >= settings["recent"]:
break
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(directory + "/assets/svg/" + sorted_recent[key]["unicode"] + ".svg", iconsizes[1], iconsizes[2])
img = Gtk.Image.new_from_pixbuf(pixbuf)
recent_items[i].set_image(img)
recent_items[i].set_label(sorted_recent[key]["name"].title())
recent_items[i].show()
if "recent_" + str(i) in signals.keys():
recent_items[i].disconnect(signals["recent_" + str(i)])
signals["recent_" + str(i)] = recent_items[i].connect("activate", item_response, sorted_recent[key])
i = i + 1
# Click response
def item_response(self, w):
global recentindex, recent
# If this is a toned item with submenu, do nothing
try:
if self.get_submenu() != None:
return
except AttributeError:
pass
# Copy character to clipboard or write it
chars = w["unicode"].split("-")
output = ''
for char in chars:
output = output + '\\U' + (char.zfill(8))
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
GLib.idle_add(clipboard.set_text, output.encode('utf-8')
.decode('unicode-escape'), -1)
# Show notification
if settings["notifications"]:
Notify.init("indicator-emojitwo")
n = Notify.Notification.new(w["name"].title(), "Emoji is now in the clipboard. Paste it wherever you want!", directory + "/assets/svg/" + w["unicode"] + ".svg")
n.show()
# Remove item from recent if already present
try:
for k in list(recent.keys()):
if recent[k]["emoji_order"] == w["emoji_order"]:
del recent[k]
except ValueError:
pass
# Store item on recent
w["recent_order"] = recentindex
recentindex = recentindex + 1
recent.update({w["emoji_order"]: w})
# Remove older items if recent is too big
if len(recent) > settings["recent"]:
biggestindex = 0
for k in recent.keys():
if recent[k]["recent_order"] > biggestindex:
biggestindex = recent[k]["recent_order"]
nextbiggest = biggestindex
for _ in range (1, settings["recent"]):
nextbiggest_candidate = 0
for k in recent.keys():
if recent[k]["recent_order"] < nextbiggest and recent[k]["recent_order"] > nextbiggest_candidate:
nextbiggest_candidate = recent[k]["recent_order"]
nextbiggest = nextbiggest_candidate
minindex = nextbiggest
for k in list(recent.keys()):
if recent[k]["recent_order"] < minindex:
del recent[k]
# Save recentfile
with open(recentfile, 'w') as outfile:
json.dump(recent, outfile)
# Refresh recent icons submenu
GLib.idle_add(refresh_recent_submenu)
def get_emoji_group(code):
for key in groups_data:
if code in groups_data[key]["unicodes"]:
return key
return False
if __name__ == "__main__":
executable = os.path.basename(__file__)
setproctitle.setproctitle(executable)
# Create the main menu
menu = Gtk.Menu()
# Create indicator
ind = appindicator.Indicator.new("indicator-emojitwo", directory + "/assets/icon-default.svg", appindicator.IndicatorCategory.OTHER)
ind.set_status(appindicator.IndicatorStatus.ACTIVE)
# If there is a icon present in the theme, use it!
try:
icons = Gtk.IconTheme.get_default()
icon = icons.load_icon("indicator-emojitwo", Gtk.IconSize.MENU, 0)
ind.set_icon_full("indicator-emojitwo", "Emoji indicator icon depicting a smiley face")
except GLib.Error as loaderror:
logging.info(loaderror)
pass
# Get proper icon sizes
iconsizes = Gtk.IconSize.lookup(Gtk.IconSize.MENU)
# Create categories items and submenus
category_item = {}
category_menu = {}
for category in categories:
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(directory + "/assets/categories/" + category + ".svg", iconsizes[1], iconsizes[2])
img = Gtk.Image.new_from_pixbuf(pixbuf)
category_item[category] = Gtk.ImageMenuItem(label=category.title())
item_settings = category_item[category].get_settings()
item_settings.set_property('gtk-menu-images', True)
GLib.idle_add(category_item[category].set_image, img)
category_menu[category] = Gtk.Menu()
category_item[category].set_submenu(category_menu[category])
# Load groups and icons definition and rearrange them in order
with open(directory + "/assets/groups.json") as groups_file:
groups_data = json.load(groups_file)
with open(directory + "/assets/emoji.json") as json_file:
json_data = json.load(json_file)
def orderfunc(tup):
key, d = tup
return int(d["emoji_order"])
sorted_data = sorted(json_data.items(), key=orderfunc)
sorted_data = OrderedDict(sorted_data)
# Load icons into menu items
tones_re = re.compile('(.*) tone[ ]?\\d', re.IGNORECASE)
item_groups = {}
submenu_groups = {}
items = {}
tone_groups = {}
submenu_tones = {}
signals = {}
for category in categories:
for key in sorted_data:
if settings["lowend"] == False or not sorted_data[key]["unicode"] in lowend:
if sorted_data[key]["category"] == category:
emoji_group = get_emoji_group(sorted_data[key]["unicode"])
if emoji_group != False:
# Grouped emoji
if not emoji_group in item_groups:
# Create group item
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(directory + "/assets/svg/" + groups_data[emoji_group]["icon"] + ".svg", iconsizes[1], iconsizes[2])
img = Gtk.Image.new_from_pixbuf(pixbuf)
item_groups[emoji_group] = Gtk.ImageMenuItem(label=groups_data[emoji_group]["name"].title())
item_settings = item_groups[emoji_group].get_settings()
item_settings.set_property('gtk-menu-images', True)
GLib.idle_add(item_groups[emoji_group].set_image, img)
GLib.idle_add(category_menu[category].append, item_groups[emoji_group])
GLib.idle_add(item_groups[emoji_group].show)
# Create group submenu
submenu_groups[emoji_group] = Gtk.Menu()
GLib.idle_add(item_groups[emoji_group].set_submenu, submenu_groups[emoji_group])
if tones_re.match(sorted_data[key]["name"]) == None:
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(directory + "/assets/svg/" + sorted_data[key]["unicode"] + ".svg", iconsizes[1], iconsizes[2])
img = Gtk.Image.new_from_pixbuf(pixbuf)
items[sorted_data[key]["unicode"]] = Gtk.ImageMenuItem(label=sorted_data[key]["name"].title())
item_settings = items[sorted_data[key]["unicode"]].get_settings()
item_settings.set_property('gtk-menu-images', True)
GLib.idle_add(items[sorted_data[key]["unicode"]].set_image, img)
GLib.idle_add(submenu_groups[emoji_group].append, items[sorted_data[key]["unicode"]])
GLib.idle_add(items[sorted_data[key]["unicode"]].show)
signals[key] = items[sorted_data[key]["unicode"]].connect("activate", item_response, sorted_data[key])
else:
# This won't happen, we are not grouping toned icons
pass
else:
if tones_re.match(sorted_data[key]["name"]) is None:
# Not toned emoji (aka simplest case), just add to menu
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(directory + "/assets/svg/" + sorted_data[key]["unicode"] + ".svg", iconsizes[1], iconsizes[2])
img = Gtk.Image.new_from_pixbuf(pixbuf)
items[sorted_data[key]["unicode"]] = Gtk.ImageMenuItem(label=sorted_data[key]["name"].title())
item_settings = items[sorted_data[key]["unicode"]].get_settings()
item_settings.set_property('gtk-menu-images', True)
GLib.idle_add(items[sorted_data[key]["unicode"]].set_image, img)
GLib.idle_add(category_menu[category].append, items[sorted_data[key]["unicode"]])
GLib.idle_add(items[sorted_data[key]["unicode"]].show)
signals[key] = items[sorted_data[key]["unicode"]].connect("activate", item_response, sorted_data[key])
else:
# Toned emoji
if settings["toned"] == -1 and settings["lowend"] == False:
# Show all toned emojis in a submenu
original_key = key
chars = sorted_data[key]["unicode"].split("-")
tone_group = chars[0] # FIXME this can be multibyte so taking the first byt isn't right
if not tone_group in tone_groups:
# Create tone submenu
submenu_tones[tone_group] = Gtk.Menu()
GLib.idle_add(items[tone_group].set_submenu, submenu_tones[tone_group])
tone_groups[tone_group] = items[tone_group]
# Get key from original icon
for k in sorted_data:
if sorted_data[k]["unicode"] == tone_group:
key = k
# Append untoned icon to submenu
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(directory + "/assets/svg/" + sorted_data[key]["unicode"] + ".svg", iconsizes[1], iconsizes[2])
img = Gtk.Image.new_from_pixbuf(pixbuf)
items[sorted_data[key]["unicode"]] = Gtk.ImageMenuItem(label=sorted_data[key]["name"].title())
item_settings = items[tone_group].get_settings()
item_settings.set_property('gtk-menu-images', True)
GLib.idle_add(items[sorted_data[key]["unicode"]].set_image, img)
GLib.idle_add(submenu_tones[tone_group].append, items[sorted_data[key]["unicode"]])
GLib.idle_add(items[sorted_data[key]["unicode"]].show)
signals[key] = items[sorted_data[key]["unicode"]].connect("activate", item_response, sorted_data[key])
key = original_key
# Append toned emoji to toned submenu
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(directory + "/assets/svg/" + sorted_data[key]["unicode"] + ".svg", iconsizes[1], iconsizes[2])
img = Gtk.Image.new_from_pixbuf(pixbuf)
items[sorted_data[key]["unicode"]] = Gtk.ImageMenuItem(label=sorted_data[key]["name"].title())
item_settings = items[sorted_data[key]["unicode"]].get_settings()
item_settings.set_property('gtk-menu-images', True)
GLib.idle_add(items[sorted_data[key]["unicode"]].set_image, img)
GLib.idle_add(submenu_tones[tone_group].append, items[sorted_data[key]["unicode"]])
GLib.idle_add(items[sorted_data[key]["unicode"]].show)
signals[key] = items[sorted_data[key]["unicode"]].connect("activate", item_response, sorted_data[key])
else:
# Show only one tone for toned emojis
if settings["toned"] == 0 or settings["lowend"] == True:
# Untoned emojis are already shown by default, so do nothing
# When using lowend mode, untoned emojis are enforced
pass
else:
desired_tone = settings["toned"]
current_tone = re.search("\d",sorted_data[key]["name"]).group(0)
if desired_tone == int(current_tone):
# Replace item with desired tone
toned_key = key
chars = sorted_data[key]["unicode"].split("-") # FIXME this can be multibyte so taking the first byt isn't right
untoned_code = chars[0]
for k in sorted_data:
if sorted_data[k]["unicode"] == untoned_code:
key = k
# Replace image in item
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(directory + "/assets/svg/" + sorted_data[toned_key]["unicode"] + ".svg", iconsizes[1], iconsizes[2])
img = Gtk.Image.new_from_pixbuf(pixbuf)
GLib.idle_add(items[sorted_data[key]["unicode"]].set_image, img)
# Replace action
items[sorted_data[key]["unicode"]].disconnect(signals[key])
signals[key] = items[sorted_data[key]["unicode"]].connect("activate", item_response, sorted_data[toned_key])
# Load icons into recent category
recent_items = []
for i in range(0, settings["recent"]):
recent_items.append(Gtk.ImageMenuItem())
item_settings = recent_items[i].get_settings()
item_settings.set_property('gtk-menu-images', True)
category_menu["recent"].append(recent_items[i])
refresh_recent_submenu()
# Append categories to main menu
for category in categories:
GLib.idle_add(menu.append, category_item[category])
GLib.idle_add(category_item[category].show)
# Create separator
separator = Gtk.SeparatorMenuItem()
GLib.idle_add(menu.append, separator)
GLib.idle_add(separator.show)
# Create search item
search_item = Gtk.MenuItem(label=_("Search…"))
GLib.idle_add(menu.append, search_item)
GLib.idle_add(search_item.show)
search_item.connect("activate", open_search_window, "Search")
# Create settings item
settings_item = Gtk.MenuItem(label=_("Settings…"))
GLib.idle_add(menu.append, settings_item)
GLib.idle_add(settings_item.show)
settings_item.connect("activate", open_settings_window, "Settings")
# Create exit item
exit_item = Gtk.MenuItem(label=_("Quit"))
GLib.idle_add(menu.append, exit_item)
GLib.idle_add(exit_item.show)
exit_item.connect("activate", terminate)
# Associate menu with indicator
ind.set_menu(menu)
# Run server
Gtk.main()