From 9418f16a4938f53dcff8f60718413665e3ddde6b Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 9 Oct 2020 08:12:41 -0700 Subject: [PATCH 01/11] improv: Define Page enum instead of just using strings Slightly more efficient, allows exhaustive matching, etc. --- src/application/key.rs | 18 +++++++++--------- src/application/keyboard.rs | 13 ++++--------- src/application/mod.rs | 1 + src/application/page.rs | 30 ++++++++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 18 deletions(-) create mode 100644 src/application/page.rs diff --git a/src/application/key.rs b/src/application/key.rs index 7e8c8665..210867c0 100644 --- a/src/application/key.rs +++ b/src/application/key.rs @@ -3,6 +3,7 @@ use std::{ collections::HashMap, }; +use super::page::Page; use super::picker::Picker; use super::rect::Rect; @@ -28,7 +29,7 @@ pub struct Key { pub(crate) foreground_color: String, // GTK buttons by page //TODO: clean up this crap - pub(crate) gtk: HashMap, + pub(crate) gtk: HashMap, } impl Key { @@ -85,9 +86,9 @@ button {{ } pub fn refresh(&self, picker: &Picker) { - for (page, button) in self.gtk.iter() { - button.set_label(match page.as_str() { - "Layer 1" => { + for (layer, button) in self.gtk.iter() { + button.set_label(match layer { + Page::Layer1 => { let scancode_name = &self.scancodes[0].1; if let Some(picker_key) = picker.keys.get(scancode_name) { &picker_key.text @@ -95,7 +96,7 @@ button {{ scancode_name } }, - "Layer 2" => { + Page::Layer2 => { let scancode_name = &self.scancodes[1].1; if let Some(picker_key) = picker.keys.get(scancode_name) { &picker_key.text @@ -103,10 +104,9 @@ button {{ scancode_name } }, - "Keycaps" => &self.physical_name, - "Logical" => &self.logical_name, - "Electrical" => &self.electrical_name, - _ => "", + Page::Keycaps => &self.physical_name, + Page::Logical => &self.logical_name, + Page::Electrical => &self.electrical_name, }); } } diff --git a/src/application/keyboard.rs b/src/application/keyboard.rs index 5ec91217..9086c3b8 100644 --- a/src/application/keyboard.rs +++ b/src/application/keyboard.rs @@ -17,6 +17,7 @@ use crate::daemon::Daemon; use crate::keyboard::Keyboard as ColorKeyboard; use crate::keyboard_color_button::KeyboardColorButton; use super::key::Key; +use super::page::Page; use super::picker::Picker; use super::rect::Rect; @@ -448,14 +449,8 @@ button { let color_button = KeyboardColorButton::new(color_keyboard).widget().clone(); color_button.set_valign(gtk::Align::Center); - for page in &[ - "Layer 1", - "Layer 2", - "Keycaps", - "Logical", - "Electrical" - ] { - let page_label = gtk::Label::new(Some(page)); + for page in Page::iter_all() { + let page_label = gtk::Label::new(Some(page.name())); let fixed = gtk::Fixed::new(); notebook.append_page(&fixed, Some(&page_label)); @@ -514,7 +509,7 @@ button { let mut keys = self.keys.borrow_mut(); let k = &mut keys[i]; - k.gtk.insert(page.to_string(), button); + k.gtk.insert(page, button); k.refresh(&self.picker); } } diff --git a/src/application/mod.rs b/src/application/mod.rs index db932f28..1b5e8a78 100644 --- a/src/application/mod.rs +++ b/src/application/mod.rs @@ -7,6 +7,7 @@ use crate::daemon::{Daemon, DaemonClient, daemon_server}; mod key; mod keyboard; +mod page; mod picker; mod rect; diff --git a/src/application/page.rs b/src/application/page.rs new file mode 100644 index 00000000..861406b3 --- /dev/null +++ b/src/application/page.rs @@ -0,0 +1,30 @@ +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum Page { + Layer1, + Layer2, + Keycaps, + Logical, + Electrical, +} + +impl Page { + pub fn name(&self) -> &'static str { + match self { + Self::Layer1 => "Layer 1", + Self::Layer2 => "Layer 2", + Self::Keycaps => "Keycaps", + Self::Logical => "Logical", + Self::Electrical => "Electrical" + } + } + + pub fn iter_all() -> impl Iterator { + vec![ + Self::Layer1, + Self::Layer2, + Self::Keycaps, + Self::Logical, + Self::Electrical, + ].into_iter() + } +} \ No newline at end of file From c20ef72b03529e86546b91fbc262de13aa57dbf1 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 9 Oct 2020 08:47:32 -0700 Subject: [PATCH 02/11] improv: Center main vbox I think it previously looked pretty bad full screen on a large display. This is an improvement, anyway. --- src/application/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/application/mod.rs b/src/application/mod.rs index 1b5e8a78..5610b2f7 100644 --- a/src/application/mod.rs +++ b/src/application/mod.rs @@ -17,6 +17,7 @@ use keyboard::Keyboard; fn main_keyboard(app: >k::Application, keyboard: Rc) { let vbox = cascade! { gtk::Box::new(gtk::Orientation::Vertical, 32); + ..set_halign(gtk::Align::Center); ..add(&keyboard.clone().gtk()); ..add(&keyboard.clone().picker()); }; From aedec397d140a61a97b64ec0b100740cea2da554 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 12 Oct 2020 08:58:14 -0700 Subject: [PATCH 03/11] fix: Margin in vbox instead of border on window --- src/application/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/application/mod.rs b/src/application/mod.rs index 5610b2f7..39712781 100644 --- a/src/application/mod.rs +++ b/src/application/mod.rs @@ -17,6 +17,7 @@ use keyboard::Keyboard; fn main_keyboard(app: >k::Application, keyboard: Rc) { let vbox = cascade! { gtk::Box::new(gtk::Orientation::Vertical, 32); + ..set_property_margin(10); ..set_halign(gtk::Align::Center); ..add(&keyboard.clone().gtk()); ..add(&keyboard.clone().picker()); @@ -30,7 +31,6 @@ fn main_keyboard(app: >k::Application, keyboard: Rc) { let window = cascade! { gtk::ApplicationWindow::new(app); ..set_title("Keyboard Layout"); - ..set_border_width(10); ..set_position(gtk::WindowPosition::Center); ..set_default_size(1024, 768); ..add(&scrolled_window); From 367bd89e40312d3a0047d2820c9a2aa52e13b4bb Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 12 Oct 2020 21:02:28 -0700 Subject: [PATCH 04/11] improv: Wrap label onto two lines depending on length --- layouts/picker.csv | 66 ++++++++++++++++++------------------- src/application/key.rs | 10 +++--- src/application/keyboard.rs | 24 +++++++++++--- 3 files changed, 57 insertions(+), 43 deletions(-) diff --git a/layouts/picker.csv b/layouts/picker.csv index ed2a5419..fe17ccfb 100644 --- a/layouts/picker.csv +++ b/layouts/picker.csv @@ -39,14 +39,14 @@ Number keys,4,1 0,),0 ,, Modifier keys,4,1 -LEFT_ALT,Left,Alt -RIGHT_ALT,Right,Alt -LEFT_CTRL,Left,Ctrl -RIGHT_CTRL,Right,Ctrl -LEFT_SHIFT,Left,Shift -RIGHT_SHIFT,Right,Shift -LEFT_SUPER,Left,Super -RIGHT_SUPER,Right,Super +LEFT_ALT,Left Alt, +RIGHT_ALT,Right Alt, +LEFT_CTRL,Left Ctrl, +RIGHT_CTRL,Right Ctrl, +LEFT_SHIFT,Left Shift, +RIGHT_SHIFT,Right Shift, +LEFT_SUPER,Left Super, +RIGHT_SUPER,Right Super, ,, Actions,4,1 ENTER,Enter, @@ -77,7 +77,7 @@ F11,F11, F12,F12, ,, Numpad,6,1 -NUM_LOCK,Num,Lock +NUM_LOCK,Num Lock, NUM_7,7, NUM_8,8, NUM_9,9, @@ -120,35 +120,35 @@ END,End, ,, Media,3,1 MUTE,Mute, -VOLUME_UP,Vol,Up -VOLUME_DOWN,Vol,Down -PLAY_PAUSE,Play,Pause -MEDIA_NEXT,Next,Track -MEDIA_PREV,Prev,Track +VOLUME_UP,Vol Up, +VOLUME_DOWN,Vol Down, +PLAY_PAUSE,Play Pause, +MEDIA_NEXT,Next Track, +MEDIA_PREV,Prev Track, ,, Controls,4,2 -DISPLAY_TOGGLE,Screen,Toggle -BRIGHTNESS_UP,Screen,Up -BRIGHTNESS_DOWN,Screen,Down -DISPLAY_MODE,Screen,Mode +DISPLAY_TOGGLE,Screen Toggle, +BRIGHTNESS_UP,Screen Up, +BRIGHTNESS_DOWN,Screen Down, +DISPLAY_MODE,Screen Mode, SUSPEND,Suspend, -CAMERA_TOGGLE,Camera,Toggle -AIRPLANE_MODE,Airplane,Mode +CAMERA_TOGGLE,Camera Toggle, +AIRPLANE_MODE,Airplane Mode, TOUCHPAD,Touchpad,Toggle ,, LED controls,4,1 -KBD_TOGGLE,LED,Toggle -KBD_UP,LED,Up -KBD_DOWN,LED,Down -KBD_BKL,LED,Cycle -KBD_COLOR,LED,Color +KBD_TOGGLE,LED Toggle, +KBD_UP,LED Up, +KBD_DOWN,LED Down, +KBD_BKL,LED Cycle, +KBD_COLOR,LED Color, ,, Layer keys,4,1 -LAYER_ACCESS_1,Access,Layer 1 -FN,Access,Layer 2 -LAYER_ACCESS_3,Access,Layer 3 -LAYER_ACCESS_4,Access,Layer 4 -LAYER_TOGGLE_1,Toggle,Layer 1 -LAYER_TOGGLE_2,Toggle,Layer 2 -LAYER_TOGGLE_3,Toggle,Layer 3 -LAYER_TOGGLE_4,Toggle,Layer 4 +LAYER_ACCESS_1,Access Layer 1, +FN,Access Layer 2, +LAYER_ACCESS_3,Access Layer 3, +LAYER_ACCESS_4,Access Layer 4, +LAYER_TOGGLE_1,Toggle Layer 1, +LAYER_TOGGLE_2,Toggle Layer 2, +LAYER_TOGGLE_3,Toggle Layer 3, +LAYER_TOGGLE_4,Toggle Layer 4, diff --git a/src/application/key.rs b/src/application/key.rs index 210867c0..8f526e57 100644 --- a/src/application/key.rs +++ b/src/application/key.rs @@ -29,7 +29,7 @@ pub struct Key { pub(crate) foreground_color: String, // GTK buttons by page //TODO: clean up this crap - pub(crate) gtk: HashMap, + pub(crate) gtk: HashMap, } impl Key { @@ -60,7 +60,7 @@ button {{ } pub fn select(&self, picker: &Picker, layer: usize) { - for (_page, button) in self.gtk.iter() { + for (_page, (button, _label)) in self.gtk.iter() { button.get_style_context().add_class("selected"); } if let Some((_scancode, scancode_name)) = self.scancodes.get(layer) { @@ -73,7 +73,7 @@ button {{ } pub fn deselect(&self, picker: &Picker, layer: usize) { - for (_page, button) in self.gtk.iter() { + for (_page, (button, _label)) in self.gtk.iter() { button.get_style_context().remove_class("selected"); } if let Some((_scancode, scancode_name)) = self.scancodes.get(layer) { @@ -86,8 +86,8 @@ button {{ } pub fn refresh(&self, picker: &Picker) { - for (layer, button) in self.gtk.iter() { - button.set_label(match layer { + for (layer, (_button, label)) in self.gtk.iter() { + label.set_label(match layer { Page::Layer1 => { let scancode_name = &self.scancodes[0].1; if let Some(picker_key) = picker.keys.get(scancode_name) { diff --git a/src/application/keyboard.rs b/src/application/keyboard.rs index 9086c3b8..fb546629 100644 --- a/src/application/keyboard.rs +++ b/src/application/keyboard.rs @@ -315,12 +315,18 @@ button { } for key in group.keys.iter() { + let label = cascade! { + gtk::Label::new(Some(&key.text)); + ..set_line_wrap(true); + ..set_max_width_chars(1); + ..set_justify(gtk::Justification::Center); + }; + let button = cascade! { gtk::Button::new(); - ..set_hexpand(false); ..set_size_request(48 * group.width, 48); - ..set_label(&key.text); ..get_style_context().add_provider(&style_provider, gtk::STYLE_PROVIDER_PRIORITY_USER); + ..add(&label); }; // Check that scancode is available for the keyboard @@ -456,7 +462,7 @@ button { let keys_len = self.keys.borrow().len(); for i in 0..keys_len { - let button = { + let (button, label) = { let keys = self.keys.borrow(); let k = &keys[i]; @@ -473,15 +479,23 @@ button { ..load_from_data(css.as_bytes()).expect("Failed to parse css"); }; + let label = cascade! { + gtk::Label::new(None); + ..set_line_wrap(true); + ..set_justify(gtk::Justification::Center); + }; + let button = cascade! { gtk::Button::new(); ..set_focus_on_click(false); ..set_size_request(w, h); ..get_style_context().add_provider(&style_provider, gtk::STYLE_PROVIDER_PRIORITY_USER); + ..add(&label); }; fixed.put(&button, x, y); - button + + (button, label) }; { @@ -509,7 +523,7 @@ button { let mut keys = self.keys.borrow_mut(); let k = &mut keys[i]; - k.gtk.insert(page, button); + k.gtk.insert(page, (button, label)); k.refresh(&self.picker); } } From e5beab559ae89f80104f6cc258719b3e247ec40e Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 13 Oct 2020 10:15:51 -0700 Subject: [PATCH 05/11] improv: Make ColorCircle a gtk widget --- src/color_circle.rs | 97 ++++++++++++++++++++++++++++++--------------- src/lib.rs | 3 ++ 2 files changed, 67 insertions(+), 33 deletions(-) diff --git a/src/color_circle.rs b/src/color_circle.rs index 333877a1..21509687 100644 --- a/src/color_circle.rs +++ b/src/color_circle.rs @@ -1,11 +1,13 @@ use cascade::cascade; use glib::clone; -use glib::clone::{Downgrade, Upgrade}; +use glib::subclass; +use glib::subclass::prelude::*; use gtk::prelude::*; +use gtk::subclass::prelude::*; +use glib::translate::{FromGlibPtrFull, ToGlib, ToGlibPtr}; use std::cell::Cell; use std::f64::consts::PI; use std::ptr; -use std::rc::{Rc, Weak}; use crate::color::Rgb; @@ -32,29 +34,19 @@ pub struct ColorCircleInner { symbol: Cell, } -#[derive(Clone)] -pub struct ColorCircle(Rc); +impl ObjectSubclass for ColorCircleInner { + const NAME: &'static str = "S76ColorCircle"; -pub struct ColorCircleWeak(Weak); + type ParentType = gtk::Bin; -impl Downgrade for ColorCircle { - type Weak = ColorCircleWeak; + type Instance = subclass::simple::InstanceStruct; + type Class = subclass::simple::ClassStruct; - fn downgrade(&self) -> Self::Weak { - ColorCircleWeak(self.0.downgrade()) - } -} + glib_object_subclass!(); -impl Upgrade for ColorCircleWeak { - type Strong = ColorCircle; + fn class_init(_klass: &mut subclass::simple::ClassStruct) {} - fn upgrade(&self) -> Option { - self.0.upgrade().map(ColorCircle) - } -} - -impl ColorCircle { - pub fn new(size: i32) -> Self { + fn new() -> Self { let drawing_area = gtk::DrawingArea::new(); let provider = cascade! { @@ -73,28 +65,67 @@ impl ColorCircle { let frame = cascade! { gtk::AspectFrame::new(None, 0., 0., 1., false); ..set_shadow_type(gtk::ShadowType::None); - ..set_size_request(size, size); ..add(&button); }; - let color_circle = Self(Rc::new(ColorCircleInner { + Self { frame, drawing_area, button, rgb: Cell::new(Rgb::new(0, 0, 0)), symbol: Cell::new(ColorCircleSymbol::None), alpha: Cell::new(1.), - })); + } + } +} +impl ObjectImpl for ColorCircleInner { + glib_object_impl!(); + + fn constructed(&self, obj: &glib::Object) { + self.parent_constructed(obj); + + let obj: &ColorCircle = obj.downcast_ref().unwrap(); + obj.add(&self.frame); + } +} + +impl WidgetImpl for ColorCircleInner {} +impl ContainerImpl for ColorCircleInner {} +impl BinImpl for ColorCircleInner {} + +glib_wrapper! { + pub struct ColorCircle( + Object, + subclass::simple::ClassStruct, ColorCircleClass>) + @extends gtk::Bin, gtk::Container, gtk::Widget; + + match fn { + get_type => || ColorCircleInner::get_type().to_glib(), + } +} + +impl ColorCircle { + pub fn new(size: i32) -> Self { + let color_circle: Self = glib::Object::new(Self::static_type(), &[]) + .unwrap() + .downcast() + .unwrap(); + + color_circle.inner().frame.set_size_request(size, size); color_circle.connect_signals(); color_circle } + fn inner(&self) -> &ColorCircleInner { + ColorCircleInner::from_instance(self) + } + fn connect_signals(&self) { let self_ = self; - self.0 + self.inner() .drawing_area .connect_draw(clone!(@strong self_ => move |w, cr| { self_.draw(w, cr); @@ -105,13 +136,13 @@ impl ColorCircle { // `arbitrary_self_types` feature would allow `self: &Rc` pub fn connect_clicked(&self, cb: F) { let self_ = self; - self.0 + self.inner() .button .connect_clicked(clone!(@weak self_ => @default-panic, move |_| cb(&self_))); } pub fn widget(&self) -> >k::Widget { - self.0.frame.upcast_ref::() + self.upcast_ref() } fn draw(&self, w: >k::DrawingArea, cr: &cairo::Context) { @@ -120,7 +151,7 @@ impl ColorCircle { let radius = width.min(height) / 2.; let (r, g, b) = self.rgb().to_floats(); - let alpha = self.0.alpha.get(); + let alpha = self.inner().alpha.get(); cr.arc(radius, radius, radius, 0., 2. * PI); cr.set_source_rgba(r, g, b, alpha); @@ -130,7 +161,7 @@ impl ColorCircle { cr.set_source_rgb(0.25, 0.25, 0.25); cr.set_line_width(1.5); - match self.0.symbol.get() { + match self.inner().symbol.get() { ColorCircleSymbol::Plus => { cr.move_to(radius, 0.8 * radius); cr.line_to(radius, 1.2 * radius); @@ -149,24 +180,24 @@ impl ColorCircle { } pub fn set_rgb(&self, color: Rgb) { - self.0.rgb.set(color); + self.inner().rgb.set(color); self.widget().queue_draw(); } pub fn rgb(&self) -> Rgb { - self.0.rgb.get() + self.inner().rgb.get() } pub fn set_symbol(&self, symbol: ColorCircleSymbol) { - self.0.symbol.set(symbol); + self.inner().symbol.set(symbol); self.widget().queue_draw(); } pub fn set_alpha(&self, alpha: f64) { - self.0.alpha.set(alpha); + self.inner().alpha.set(alpha); } pub fn ptr_eq(&self, other: &Self) -> bool { - ptr::eq(self.0.as_ref(), other.0.as_ref()) + ptr::eq(self.inner(), other.inner()) } } diff --git a/src/lib.rs b/src/lib.rs index 9b93cad7..43275fb8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,6 @@ +#[macro_use] +extern crate glib; + pub mod application; pub mod daemon; From 4647494061c24c5b581e013d2c61ee7764bd8513 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 13 Oct 2020 10:39:27 -0700 Subject: [PATCH 06/11] improv: Make KeyboardColorButton a GTK widget This makes it possible to remove a `@strong` reference, preventing a reference cycle. Previously there was a reference cycle because the GTK widget needed a reference to `KeyboardColorButtonInner` for callbacks, and likewise that struct contained a reference to `KeyboardColorButtonInner`. The reference in the callback couldn't be weak, because in some uses it is expected that `KeyboardColorButton` may be dropped by code using the type, to just use the GTK widget. This adds a bit of boilerplate, but it's probably the cleanest way to avoid reference cycles. The cycle here prevented the `DaemonClient`'s drop from being called. --- src/keyboard_color_button.rs | 144 ++++++++++++++++++++++------------- 1 file changed, 90 insertions(+), 54 deletions(-) diff --git a/src/keyboard_color_button.rs b/src/keyboard_color_button.rs index c75d9e41..b02cd1e5 100644 --- a/src/keyboard_color_button.rs +++ b/src/keyboard_color_button.rs @@ -1,17 +1,19 @@ use cascade::cascade; use glib::clone; -use glib::clone::{Downgrade, Upgrade}; +use glib::subclass; +use glib::subclass::prelude::*; use gtk::prelude::*; +use gtk::subclass::prelude::*; +use glib::translate::{FromGlibPtrFull, ToGlib, ToGlibPtr}; use std::cell::RefCell; use std::iter; -use std::rc::{Rc, Weak}; use crate::choose_color::choose_color; use crate::color::Rgb; use crate::color_circle::{ColorCircle, ColorCircleSymbol}; use crate::keyboard::Keyboard; -struct KeyboardColorButtonInner { +pub struct KeyboardColorButtonInner { button: ColorCircle, circles: RefCell>, grid: gtk::Grid, @@ -19,32 +21,22 @@ struct KeyboardColorButtonInner { add_circle: ColorCircle, remove_button: gtk::Button, edit_button: gtk::Button, - keyboard: Keyboard, + keyboard: RefCell, } -#[derive(Clone)] -pub struct KeyboardColorButton(Rc); +impl ObjectSubclass for KeyboardColorButtonInner { + const NAME: &'static str = "S76KeyboardColorButton"; -pub struct KeyboardColorButtonWeak(Weak); + type ParentType = gtk::Bin; -impl Downgrade for KeyboardColorButton { - type Weak = KeyboardColorButtonWeak; + type Instance = subclass::simple::InstanceStruct; + type Class = subclass::simple::ClassStruct; - fn downgrade(&self) -> Self::Weak { - KeyboardColorButtonWeak(self.0.downgrade()) - } -} - -impl Upgrade for KeyboardColorButtonWeak { - type Strong = KeyboardColorButton; + glib_object_subclass!(); - fn upgrade(&self) -> Option { - self.0.upgrade().map(KeyboardColorButton) - } -} + fn class_init(_klass: &mut subclass::simple::ClassStruct) {} -impl KeyboardColorButton { - pub fn new(keyboard: Keyboard) -> Self { + fn new() -> Self { let grid = cascade! { gtk::Grid::new(); ..set_column_spacing(6); @@ -76,14 +68,9 @@ impl KeyboardColorButton { let button = cascade! { ColorCircle::new(30); - ..set_rgb(keyboard.color().unwrap()); ..connect_clicked(clone!(@weak popover => @default-panic, move |_| popover.popup())); }; - keyboard.connect_color_changed(clone!(@weak button => @default-panic, move |_, color| { - button.set_rgb(color); - })); - popover.set_relative_to(Some(button.widget())); let add_circle = cascade! { @@ -92,7 +79,9 @@ impl KeyboardColorButton { ..set_symbol(ColorCircleSymbol::Plus); }; - let keyboard_color_button = Self(Rc::new(KeyboardColorButtonInner { + let keyboard = Keyboard::new_dummy(); + + Self { button, circles: RefCell::new(Vec::new()), grid, @@ -100,7 +89,50 @@ impl KeyboardColorButton { add_circle, remove_button, edit_button, - keyboard, + keyboard: RefCell::new(keyboard), + } + } +} + +impl ObjectImpl for KeyboardColorButtonInner { + glib_object_impl!(); + + fn constructed(&self, obj: &glib::Object) { + self.parent_constructed(obj); + + let obj: &KeyboardColorButton = obj.downcast_ref().unwrap(); + obj.add(&self.button); + } +} + +impl WidgetImpl for KeyboardColorButtonInner {} +impl ContainerImpl for KeyboardColorButtonInner {} +impl BinImpl for KeyboardColorButtonInner {} + +glib_wrapper! { + pub struct KeyboardColorButton( + Object, + subclass::simple::ClassStruct, KeyboardColorButtonClass>) + @extends gtk::Bin, gtk::Container, gtk::Widget; + + match fn { + get_type => || KeyboardColorButtonInner::get_type().to_glib(), + } +} + +impl KeyboardColorButton { + pub fn new(keyboard: Keyboard) -> Self { + let keyboard_color_button: Self = glib::Object::new(Self::static_type(), &[]) + .unwrap() + .downcast() + .unwrap(); + + keyboard_color_button.inner().keyboard.replace(keyboard.clone()); + keyboard_color_button.inner().button.set_rgb(keyboard.color().unwrap()); + + let button = &keyboard_color_button.inner().button; + keyboard.connect_color_changed(clone!(@weak button => @default-panic, move |_, color| { + button.set_rgb(color); })); keyboard_color_button.connect_signals(); @@ -122,20 +154,24 @@ impl KeyboardColorButton { keyboard_color_button } + fn inner(&self) -> &KeyboardColorButtonInner { + KeyboardColorButtonInner::from_instance(self) + } + fn connect_signals(&self) { let self_ = self; - self.0 + self.inner() .add_circle - .connect_clicked(clone!(@strong self_ => move |_| { + .connect_clicked(clone!(@weak self_ => move |_| { self_.add_clicked(); })); - self.0.remove_button.connect_clicked( + self.inner().remove_button.connect_clicked( clone!(@weak self_ => @default-panic, move |_| self_.remove_clicked()), ); - self.0 + self.inner() .edit_button .connect_clicked(clone!(@weak self_ => @default-panic, move |_| self_.edit_clicked())); } @@ -147,78 +183,78 @@ impl KeyboardColorButton { ..connect_clicked(clone!(@weak self_ => @default-panic, move |c| self_.circle_clicked(c))); ..set_rgb(color); }; - self.0.circles.borrow_mut().push(circle); + self.inner().circles.borrow_mut().push(circle); } fn populate_grid(&self) { - self.0.grid.foreach(|w| self.0.grid.remove(w)); + self.inner().grid.foreach(|w| self.inner().grid.remove(w)); - let circles = self.0.circles.borrow(); + let circles = self.inner().circles.borrow(); for (i, circle) in circles .iter() - .chain(iter::once(&self.0.add_circle)) + .chain(iter::once(&self.inner().add_circle)) .enumerate() { let x = i as i32 % 3; let y = i as i32 / 3; - self.0.grid.attach(circle.widget(), x, y, 1, 1); + self.inner().grid.attach(circle.widget(), x, y, 1, 1); } - self.0.grid.show_all(); + self.inner().grid.show_all(); } fn add_clicked(&self) { - if let Some(color) = choose_color(self.0.keyboard.clone(), self.widget(), "Add Color", None) + if let Some(color) = choose_color(self.inner().keyboard.borrow().clone(), self.widget(), "Add Color", None) { self.add_color(color); - self.0.remove_button.set_visible(true); + self.inner().remove_button.set_visible(true); self.populate_grid(); } else { - if let Some(circle) = &*self.0.current_circle.borrow() { - self.0.keyboard.set_color(circle.rgb()); + if let Some(circle) = &*self.inner().current_circle.borrow() { + self.inner().keyboard.borrow().set_color(circle.rgb()); } } } fn remove_clicked(&self) { - if let Some(current_circle) = &mut *self.0.current_circle.borrow_mut() { - let mut circles = self.0.circles.borrow_mut(); + if let Some(current_circle) = &mut *self.inner().current_circle.borrow_mut() { + let mut circles = self.inner().circles.borrow_mut(); if let Some(index) = circles.iter().position(|c| c.ptr_eq(current_circle)) { circles.remove(index); *current_circle = circles[index.saturating_sub(1)].clone(); current_circle.set_symbol(ColorCircleSymbol::Check); } - self.0.remove_button.set_visible(circles.len() > 1); + self.inner().remove_button.set_visible(circles.len() > 1); } self.populate_grid(); } fn edit_clicked(&self) { - if let Some(circle) = &*self.0.current_circle.borrow() { + if let Some(circle) = &*self.inner().current_circle.borrow() { if let Some(color) = choose_color( - self.0.keyboard.clone(), + self.inner().keyboard.borrow().clone(), self.widget(), "Edit Color", Some(circle.rgb()), ) { circle.set_rgb(color); } else { - self.0.keyboard.set_color(circle.rgb()); + self.inner().keyboard.borrow().set_color(circle.rgb()); } } } fn circle_clicked(&self, circle: &ColorCircle) { let color = circle.rgb(); - self.0.keyboard.set_color(color); - self.0.button.set_rgb(color); + self.inner().keyboard.borrow().set_color(color); + self.inner().button.set_rgb(color); circle.set_symbol(ColorCircleSymbol::Check); - let old_circle = self.0.current_circle.replace(Some(circle.clone())); + let old_circle = self.inner().current_circle.replace(Some(circle.clone())); old_circle.map(|c| c.set_symbol(ColorCircleSymbol::None)); } pub fn widget(&self) -> >k::Widget { - self.0.button.widget() + self.upcast_ref() } -} +} \ No newline at end of file From 138e397c6a5d9f481f66eab548c312240dbf3585 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 13 Oct 2020 10:45:09 -0700 Subject: [PATCH 07/11] improv: Wait for child process in DaemonClient::drop This avoids the ugly main loop code, and prevents some error messages that was causing. --- examples/daemon.rs | 7 +------ src/application/mod.rs | 23 ++++++----------------- src/daemon/client.rs | 31 +++++++++++++++++++++---------- 3 files changed, 28 insertions(+), 33 deletions(-) diff --git a/examples/daemon.rs b/examples/daemon.rs index b91910ef..22b1d74d 100644 --- a/examples/daemon.rs +++ b/examples/daemon.rs @@ -50,12 +50,7 @@ fn with_daemon)>(f: F) { let stdin = child.stdin.take().expect("Failed to get stdin of daemon"); let stdout = child.stdout.take().expect("Failed to get stdout of daemon"); - f(Box::new(DaemonClient::new(stdout, stdin))); - - let status = child.wait().expect("Failed to wait for daemon"); - if ! status.success() { - panic!("Failed to run daemon with exit status {:?}", status); - } + f(Box::new(DaemonClient::new(child, stdout, stdin))); } #[cfg(not(target_os = "linux"))] diff --git a/src/application/mod.rs b/src/application/mod.rs index 39712781..c92df509 100644 --- a/src/application/mod.rs +++ b/src/application/mod.rs @@ -41,7 +41,6 @@ fn main_keyboard(app: >k::Application, keyboard: Rc) { window.connect_destroy(|_| { eprintln!("Window close"); - gtk::main_quit(); }); } @@ -64,7 +63,7 @@ fn main_app(app: >k::Application, daemon: Rc) { } #[cfg(target_os = "linux")] -fn with_daemon)>(f: F) { +fn daemon() -> Rc { use std::{ process::{ Command, @@ -75,8 +74,7 @@ fn with_daemon)>(f: F) { if unsafe { libc::geteuid() == 0 } { eprintln!("Already running as root"); let server = daemon_server().expect("Failed to create server"); - f(Rc::new(server)); - return; + return Rc::new(server); } // Use pkexec to spawn daemon as superuser @@ -97,18 +95,13 @@ fn with_daemon)>(f: F) { let stdin = child.stdin.take().expect("Failed to get stdin of daemon"); let stdout = child.stdout.take().expect("Failed to get stdout of daemon"); - f(Rc::new(DaemonClient::new(stdout, stdin))); - - let status = child.wait().expect("Failed to wait for daemon"); - if ! status.success() { - panic!("Failed to run daemon with exit status {:?}", status); - } + Rc::new(DaemonClient::new(child, stdout, stdin)) } #[cfg(not(target_os = "linux"))] -fn with_daemon)>(f: F) { +fn daemon() -> Rc { let server = daemon_server().expect("Failed to create server"); - f(Rc::new(server)); + Rc::new(server) } #[cfg(target_os = "macos")] @@ -161,11 +154,7 @@ pub fn run(args: Vec) -> i32 { eprintln!("Focusing current window"); window.present(); } else { - with_daemon(|daemon| { - main_app(app, daemon); - //TODO: is this the best way to keep the daemon running? - gtk::main(); - }); + main_app(app, daemon()); } }); diff --git a/src/daemon/client.rs b/src/daemon/client.rs index 2fd59ae7..1a2d31f5 100644 --- a/src/daemon/client.rs +++ b/src/daemon/client.rs @@ -3,9 +3,13 @@ use std::{ io::{ BufRead, BufReader, - Read, Write, }, + process::{ + Child, + ChildStdin, + ChildStdout, + }, }; use super::{ @@ -16,21 +20,23 @@ use super::{ DaemonResponse, }; -pub struct DaemonClient { - read: RefCell>, - write: RefCell, +pub struct DaemonClient { + child: Child, + read: RefCell>, + write: RefCell, } -impl DaemonClient { - pub fn new(read: R, write: W) -> Self { +impl DaemonClient { + pub fn new(child: Child, stdout: ChildStdout, stdin: ChildStdin) -> Self { Self { - read: RefCell::new(BufReader::new(read)), - write: RefCell::new(write), + child, + read: RefCell::new(BufReader::new(stdout)), + write: RefCell::new(stdin), } } } -impl DaemonClientTrait for DaemonClient { +impl DaemonClientTrait for DaemonClient { fn send_command(&self, command: DaemonCommand) -> Result { let mut command_json = serde_json::to_string(&command).map_err(err_str)?; command_json.push('\n'); @@ -42,8 +48,13 @@ impl DaemonClientTrait for DaemonClient Drop for DaemonClient { +impl Drop for DaemonClient { fn drop(&mut self) { let _ = self.exit(); + + let status = self.child.wait().expect("Failed to wait for daemon"); + if !status.success() { + panic!("Failed to run daemon with exit status {:?}", status); + } } } From 19d193e5de2f305ede2235e7fcef167630df1c04 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 13 Oct 2020 13:37:32 -0700 Subject: [PATCH 08/11] improv: Add margins to buttons It will no longer allow the label text to press right against the border. Usually, this means word-wrapping is applied. --- src/application/keyboard.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/application/keyboard.rs b/src/application/keyboard.rs index fb546629..c2211b1a 100644 --- a/src/application/keyboard.rs +++ b/src/application/keyboard.rs @@ -319,6 +319,8 @@ button { gtk::Label::new(Some(&key.text)); ..set_line_wrap(true); ..set_max_width_chars(1); + ..set_margin_start(5); + ..set_margin_end(5); ..set_justify(gtk::Justification::Center); }; @@ -482,6 +484,8 @@ button { let label = cascade! { gtk::Label::new(None); ..set_line_wrap(true); + ..set_margin_start(5); + ..set_margin_end(5); ..set_justify(gtk::Justification::Center); }; From 915c4eeeec815e0bc23e1274c1a02d7fa7320f8b Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 13 Oct 2020 14:04:49 -0700 Subject: [PATCH 09/11] improv: Use `Page` struct for `Keyboard.page` There's some ugly use of `get_data()` here... If we had a GTK widget inheriting `gtk::Fixed` to use in it's place... GTK is a pain. --- src/application/keyboard.rs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/application/keyboard.rs b/src/application/keyboard.rs index c2211b1a..b5f37438 100644 --- a/src/application/keyboard.rs +++ b/src/application/keyboard.rs @@ -2,7 +2,10 @@ use cascade::cascade; use gtk::prelude::*; use serde_json::Value; use std::{ - cell::RefCell, + cell::{ + Cell, + RefCell, + }, char, collections::HashMap, fs, @@ -26,7 +29,7 @@ pub struct Keyboard { daemon_board: usize, keymap: HashMap, keys: RefCell>, - page: RefCell, + page: Cell, picker: Picker, selected: RefCell>, } @@ -241,7 +244,7 @@ impl Keyboard { daemon_board, keymap, keys: RefCell::new(keys), - page: RefCell::new(0), + page: Cell::new(Page::Layer1), picker: Picker::new(), selected: RefCell::new(None), }) @@ -249,9 +252,9 @@ impl Keyboard { pub fn layer(&self) -> usize { //TODO: make this more robust - match *self.page.borrow() { - 0 => 0, // Layer 1 - 1 => 1, // Layer 2 + match self.page.get() { + Page::Layer1 => 0, + Page::Layer2 => 1, _ => 0, // Any other page selects Layer 1 } } @@ -394,10 +397,12 @@ button { pub fn gtk(self: Rc) -> gtk::Box { let notebook = gtk::Notebook::new(); let kb = self.clone(); - notebook.connect_switch_page(move |_, _, page| { - println!("{}", page); + notebook.connect_switch_page(move |_, fixed, _| { + let page: Option<&Page> = unsafe { fixed.get_data("keyboard_confurator_page") }; + + println!("{:?}", page); let last_layer = kb.layer(); - *kb.page.borrow_mut() = page; + kb.page.set(*page.unwrap_or(&Page::Layer1)); let layer = kb.layer(); if layer != last_layer { if let Some(i) = *kb.selected.borrow() { @@ -462,6 +467,9 @@ button { let fixed = gtk::Fixed::new(); notebook.append_page(&fixed, Some(&page_label)); + // TODO: Replace with something type-safe + unsafe { fixed.set_data("keyboard_confurator_page", page) }; + let keys_len = self.keys.borrow().len(); for i in 0..keys_len { let (button, label) = { From d01055c2bdb937e668ce6d01ed362b47adacf69e Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 13 Oct 2020 14:09:17 -0700 Subject: [PATCH 10/11] improv: Use gtk::Stack, gtk::StackSwitcher instead of gtk::Notebook This way we can place the `gtk::StackSwitcher` anywhere. For instance, in the headerbar as the mockup shows. --- src/application/keyboard.rs | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/application/keyboard.rs b/src/application/keyboard.rs index b5f37438..abc5d16c 100644 --- a/src/application/keyboard.rs +++ b/src/application/keyboard.rs @@ -395,14 +395,20 @@ button { } pub fn gtk(self: Rc) -> gtk::Box { - let notebook = gtk::Notebook::new(); + let stack = cascade! { + gtk::Stack::new(); + ..set_transition_duration(0); + }; let kb = self.clone(); - notebook.connect_switch_page(move |_, fixed, _| { - let page: Option<&Page> = unsafe { fixed.get_data("keyboard_confurator_page") }; + stack.connect_property_visible_child_notify(move |stack| { + let page: Option = match stack.get_visible_child() { + Some(child) => unsafe { child.get_data("keyboard_confurator_page").cloned() }, + None => None, + }; println!("{:?}", page); let last_layer = kb.layer(); - kb.page.set(*page.unwrap_or(&Page::Layer1)); + kb.page.set(page.unwrap_or(Page::Layer1)); let layer = kb.layer(); if layer != last_layer { if let Some(i) = *kb.selected.borrow() { @@ -463,9 +469,8 @@ button { color_button.set_valign(gtk::Align::Center); for page in Page::iter_all() { - let page_label = gtk::Label::new(Some(page.name())); let fixed = gtk::Fixed::new(); - notebook.append_page(&fixed, Some(&page_label)); + stack.add_titled(&fixed, page.name(), page.name()); // TODO: Replace with something type-safe unsafe { fixed.set_data("keyboard_confurator_page", page) }; @@ -540,6 +545,16 @@ button { } } + let stack_switcher = cascade! { + gtk::StackSwitcher::new(); + ..set_stack(Some(&stack)); + }; + + let toolbar = cascade!{ + gtk::Box::new(gtk::Orientation::Horizontal, 8); + ..set_center_widget(Some(&stack_switcher)); + }; + let hbox = cascade! { gtk::Box::new(gtk::Orientation::Horizontal, 8); ..add(&brightness_label); @@ -550,8 +565,9 @@ button { let vbox = cascade! { gtk::Box::new(gtk::Orientation::Vertical, 8); + ..add(&toolbar); ..add(&hbox); - ..add(¬ebook); + ..add(&stack); }; vbox From 06a4c23c524b2a5dcaee248c07af87a4902c5c82 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 16 Oct 2020 10:14:13 -0700 Subject: [PATCH 11/11] fix: Use non-breaking switch for layer keys in picker.csv This seems to be the easiest way to prevent these keys from taking three lines. I don't necessarily like dealing with issues using unicode magic like this, but I guess it makes sense that the space in something like "layer 1" would be non-breaking. --- layouts/picker.csv | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/layouts/picker.csv b/layouts/picker.csv index fe17ccfb..43e74970 100644 --- a/layouts/picker.csv +++ b/layouts/picker.csv @@ -144,11 +144,11 @@ KBD_BKL,LED Cycle, KBD_COLOR,LED Color, ,, Layer keys,4,1 -LAYER_ACCESS_1,Access Layer 1, -FN,Access Layer 2, -LAYER_ACCESS_3,Access Layer 3, -LAYER_ACCESS_4,Access Layer 4, -LAYER_TOGGLE_1,Toggle Layer 1, -LAYER_TOGGLE_2,Toggle Layer 2, -LAYER_TOGGLE_3,Toggle Layer 3, -LAYER_TOGGLE_4,Toggle Layer 4, +LAYER_ACCESS_1,Access Layer 1, +FN,Access Layer 2, +LAYER_ACCESS_3,Access Layer 3, +LAYER_ACCESS_4,Access Layer 4, +LAYER_TOGGLE_1,Toggle Layer 1, +LAYER_TOGGLE_2,Toggle Layer 2, +LAYER_TOGGLE_3,Toggle Layer 3, +LAYER_TOGGLE_4,Toggle Layer 4,