-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #87 from regolith-linux/fix/single_instance
fix: Ensure single instance using Gtk.Application
- Loading branch information
Showing
3 changed files
with
215 additions
and
184 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
using Gtk; | ||
|
||
namespace Ilia { | ||
class Application : Gtk.Application { | ||
private GLib.HashTable<string, string?> arg_map; | ||
|
||
public Application () { | ||
Object ( | ||
application_id: "org.regolith.launcher", | ||
flags: ApplicationFlags.REPLACE | ApplicationFlags.ALLOW_REPLACEMENT | ||
); | ||
} | ||
|
||
protected override bool local_command_line (ref unowned string[] args, out int exit_status) { | ||
this.arg_map = parse_args (args); | ||
if (arg_map.contains ("-h") || arg_map.contains ("--help")) print_help_and_exit (); | ||
if (arg_map.contains ("-v") || arg_map.contains ("--version")) print_version_and_exit (); | ||
args[0] = null; | ||
return base.local_command_line (ref args, out exit_status); | ||
} | ||
|
||
protected override void activate () { | ||
var window = new Ilia.DialogWindow (this.arg_map); | ||
window.set_application (this); | ||
|
||
// Grab inputs from wayland backend before showing window | ||
if (IS_SESSION_WAYLAND) { | ||
bool is_layer_shell_supported = GtkLayerShell.is_supported (); | ||
if (!is_layer_shell_supported) { | ||
stderr.printf ("The wayland compositor does not support the layer-shell protocol, aborting."); | ||
Process.exit (1); | ||
} | ||
GtkLayerShell.init_for_window (window); | ||
GtkLayerShell.set_layer(window, GtkLayerShell.Layer.OVERLAY); | ||
GtkLayerShell.set_keyboard_mode (window, GtkLayerShell.KeyboardMode.EXCLUSIVE); | ||
} | ||
|
||
initialize_style (window, arg_map); | ||
window.show_all (); | ||
|
||
// Grab inputs from X11 backend after showing window | ||
if (!IS_SESSION_WAYLAND) { | ||
Gdk.Window gdkwin = window.get_window (); | ||
var seat = grab_inputs (gdkwin); | ||
if (seat == null) { | ||
stderr.printf ("Failed to aquire access to input devices, aborting."); | ||
Process.exit (1); | ||
} | ||
window.set_seat (seat); | ||
} | ||
|
||
// Handle mouse clicks by determining if a click is in or out of bounds | ||
// If we get a mouse click out of bounds of the window, exit. | ||
window.button_press_event.connect ((event) => { | ||
int window_width = 0, window_height = 0; | ||
window.get_size (out window_width, out window_height); | ||
|
||
int mouse_x = (int) event.x; | ||
int mouse_y = (int) event.y; | ||
|
||
var click_out_bounds = ((mouse_x < 0 || mouse_y < 0) || (mouse_x > window_width || mouse_y > window_height)); | ||
|
||
if (click_out_bounds) { | ||
window.quit (); | ||
} | ||
|
||
return !click_out_bounds; | ||
}); | ||
|
||
} | ||
|
||
private void initialize_style (Gtk.Window window, HashTable<string, string ? > arg_map) { | ||
try { | ||
if (arg_map.contains ("-t") && arg_map.get ("-t") != null) { | ||
var file = File.new_for_path (arg_map.get ("-t")); | ||
|
||
if (!file.query_exists ()) { | ||
printerr ("File '%s' does not exist.\n", file.get_path ()); | ||
Process.exit (1); | ||
} | ||
Gtk.CssProvider css_provider = new Gtk.CssProvider (); | ||
css_provider.load_from_file (file); | ||
|
||
Gtk.StyleContext.add_provider_for_screen (Gdk.Screen.get_default (), css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER); | ||
} else if (!arg_map.contains ("-n")) { | ||
Gtk.CssProvider css_provider = new Gtk.CssProvider (); | ||
css_provider.load_from_data ((string) default_css); | ||
|
||
Gtk.StyleContext.add_provider_for_screen (Gdk.Screen.get_default (), css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER); | ||
} | ||
} catch (GLib.Error ex) { | ||
error ("Failed to initalize style: " + ex.message); | ||
} | ||
} | ||
|
||
// Grabs the input devices for a given window | ||
// Some systems exhibit behavior such that keyboard / mouse cannot be reliably grabbed. | ||
// As a workaround, this function will continue to attempt to grab these resources over an | ||
// increasing time window and eventually give up and exit if ultimately unable to aquire | ||
// the keyboard and mouse resources. | ||
Gdk.Seat ? grab_inputs (Gdk.Window gdkwin) { | ||
var display = gdkwin.get_display (); // Gdk.Display.get_default(); | ||
if (display == null) { | ||
stderr.printf ("Failed to get Display\n"); | ||
return null; | ||
} | ||
|
||
var seat = display.get_default_seat (); | ||
if (seat == null) { | ||
stdout.printf ("Failed to get Seat from Display\n"); | ||
return null; | ||
} | ||
|
||
int attempt = 0; | ||
Gdk.GrabStatus ? grabStatus = null; | ||
int wait_time = 1000; | ||
|
||
do { | ||
grabStatus = seat.grab (gdkwin, Gdk.SeatCapabilities.KEYBOARD | Gdk.SeatCapabilities.POINTER, true, null, null, null); | ||
if (grabStatus != Gdk.GrabStatus.SUCCESS) { | ||
attempt++; | ||
wait_time = wait_time * 2; | ||
GLib.Thread.usleep (wait_time); | ||
} | ||
} while (grabStatus != Gdk.GrabStatus.SUCCESS && attempt < 8); | ||
|
||
if (grabStatus != Gdk.GrabStatus.SUCCESS) { | ||
stderr.printf ("Aborting, failed to grab input: %d\n", grabStatus); | ||
return null; | ||
} else { | ||
return seat; | ||
} | ||
} | ||
|
||
void print_help_and_exit () { | ||
stdout.printf ("Usage: ilia [-t stylesheet] [-n] [-a] [-p page]\n"); | ||
stdout.printf ("\n\t-t: specify path to custom stylesheet.\n"); | ||
stdout.printf ("\n\t-n: no custom styles\n"); | ||
stdout.printf ("\n\t-a: load all pages\n"); | ||
stdout.printf ("\npages:\n"); | ||
stdout.printf ("\t'apps' - launch desktop applications (default)\n"); | ||
stdout.printf ("\t'terminal' - launch a terminal command\n"); | ||
stdout.printf ("\t'notifications' - launch notifications manager\n"); | ||
stdout.printf ("\t'keybindings' - launch keybindings viewer\n"); | ||
stdout.printf ("\t'textlist' - select an item from a specified list\n"); | ||
stdout.printf ("\t\t-l: page label\n"); | ||
stdout.printf ("\t\t-i: page/item icon\n"); | ||
stdout.printf ("\t\t-n: no icon\n"); | ||
stdout.printf ("\t'windows' - navigate to a window\n"); | ||
stdout.printf ("\t'tracker' - search for files by content\n"); | ||
Process.exit (0); | ||
} | ||
|
||
void print_version_and_exit () { | ||
stdout.printf ("ilia version 0.12\n"); | ||
Process.exit (0); | ||
} | ||
|
||
/** | ||
* Convert ["-v", "-s", "asdf", "-f", "qwe"] => {("-v", null), ("-s", "adsf"), ("-f", "qwe")} | ||
* Populates key of "cmd" with first arg. | ||
* NOTE: Currently does not support quoted parameter values. | ||
*/ | ||
HashTable<string, string ? > parse_args (string[] args) { | ||
var arg_hashtable = new HashTable<string, string ? >(str_hash, str_equal); | ||
|
||
if (args == null || args.length == 0) { | ||
return arg_hashtable; | ||
} | ||
|
||
string last_key = null; | ||
foreach (string token in args) { | ||
if (!arg_hashtable.contains ("cmd")) { | ||
arg_hashtable.set ("cmd", token); | ||
} else if (is_key (token)) { | ||
if (last_key != null) { | ||
arg_hashtable.set (last_key, null); | ||
} | ||
last_key = token; | ||
} else if (last_key != null) { | ||
arg_hashtable.set (last_key, token); | ||
last_key = null; | ||
} else { | ||
// ignore | ||
} | ||
} | ||
|
||
if (last_key != null) { // Trailing single param | ||
arg_hashtable.set (last_key, null); | ||
} | ||
/* | ||
foreach (var key in arg_hashtable.get_keys ()) { | ||
stdout.printf ("%s => %s\n", key, arg_hashtable.lookup(key)); | ||
} | ||
*/ | ||
|
||
return arg_hashtable; | ||
} | ||
|
||
|
||
bool is_key (string inval) { | ||
return inval.has_prefix ("-"); | ||
} | ||
} | ||
|
||
} | ||
|
Oops, something went wrong.